Является ли обертывание идиом STL для удобства чтения хорошей идеей?

В настоящее время я работаю над проектом C++, который должен иметь как можно меньше внешних зависимостей, и поэтому я в значительной степени придерживаюсь STL и Boost. До сих пор я почти исключительно жил в Qt-стране, когда дело доходит до C++. В общем, я стараюсь использовать C# и Python, когда могу.

Сегодня я хотел проверить, содержит ли std::vector определенный элемент. С Qt я бы сделал так:

QList< int > list;
list.append( 1 );
list.append( 2 );
list.append( 3 );

if ( list.contains( 2 ) )
{
    // do something
}

Красиво и читабельно. Но у std::vector нет метода contains, что было неожиданностью. Хорошо... какая идиома STL для чего-то подобного? Поискав вокруг, кажется, что это:

std::vector< int > list;
list.push_back( 1 );
list.push_back( 2 );
list.push_back( 3 );

std::vector< int >::const_iterator result =
    std::find( list.begin(), list.end(), 2 );

if ( result != list.end() )
{
    // do something
}

Это (для меня) трудночитаемо и слишком многословно. Итак, я обнаружил, что пишу вспомогательную функцию, которая принимает вектор и значение и возвращает bool в зависимости от того, было ли найдено значение или нет. По сути, шаблонный метод contains(); оболочка для вышеуказанного вызова std::find. Затем я могу использовать это так же, как в примере с Qt.

Я имею в виду несколько похожих служебных функций, которые обертывали бы другие идиомы STL ни по какой другой причине, кроме (ощущаемого) повышения удобочитаемости. Я хочу знать... это плохая идея? Другие люди делают то же самое? Я упускаю что-то важное? В какой-то момент код будет OSS, и я бы предпочел не делать что-то идиосинкразическое, что другим разработчикам C++ показалось бы странным.


person Lucas    schedule 29.07.2010    source источник
comment
@rgrig Для меня это все еще не так читаемо, как пример Qt, и, как вы сказали, не очень эффективно. Должно быть O(n/2), а не O(n).   -  person Lucas    schedule 29.07.2010
comment
Я не уверен, на что вы ссылаетесь, так как он, по-видимому, удален, но O (n/2) - это O (n). Однако с точки зрения эффективности такая оболочка может быть перегружена для наборов/карт, чтобы использовать преимущества их сортировки без каких-либо изменений в месте вызова.   -  person Dennis Zickefoose    schedule 30.07.2010


Ответы (6)


Нет ничего плохого в написании служебных функций, которые помогут вам и сделают ваш код чище. Другие люди делают то же самое. Библиотека Boost — это самый большой набор таких служебных функций и классов.

Более того, C++ Standard явно предлагает расширить стандартную библиотеку (17.3.1.2/1):

Библиотека может быть расширена программой на C++. Каждый пункт, если применимо, описывает требования, которым должны соответствовать такие расширения. Такие расширения обычно являются одним из следующих:

  • Аргументы шаблона
  • Производные классы
  • Контейнеры, итераторы и/или алгоритмы, соответствующие соглашению об интерфейсе
person Kirill V. Lyadvinsky    schedule 29.07.2010

boost делает его намного аккуратнее. Я больше никогда не использую алгоритмы на основе итератора STL. Алгоритмы, основанные на диапазоне, представляют собой гораздо более аккуратную абстракцию и приводят к более чистому коду.

#include <boost/range/algorithm/find.hpp>

void foo(){
    std::vector<int> list;
    ...
    ...
    boost::find(list, 2) != list.end()
}
person bradgonesurfing    schedule 29.07.2010

Я бы сказал, что это определенно хорошая идея. В C++ STL отсутствует многое из того, что программисты на Python/C# привыкли ожидать от стандартной библиотеки. Если вы можете сделать свой код более читабельным, приняв этот 2-3-строчный подход STL и превратив его в одну функцию, вперед!

Вот еще один пример очень похожей проблемы: мне часто нужно преобразовать int в std::string. К моему удивлению, нет простого способа сделать это с помощью STL. Итак, я написал функцию toStr, которая выполняет 2-3 строки, необходимые для помещения int в stringstream и возврата результирующего string.

Изменить: чтобы уточнить, я рекомендую искать boost решения, прежде чем создавать свои собственные. Мой пример был предназначен для демонстрации ограничений STL, но имеет альтернативную интерпретацию: «Все, что отсутствует в STL, есть в boost».

person Justin Ardini    schedule 29.07.2010
comment
У boost есть lexical_cast‹›(), который преобразует все, что можно передать потоком, в строку. - person Martin York; 29.07.2010
comment
boost::lexical_cast‹int›(str) также сделает обратное и проанализирует строку и вернет целое число. - person bradgonesurfing; 29.07.2010
comment
Собственно, это то, чем я сейчас пользуюсь. Я просто привел пример. :) - person Justin Ardini; 29.07.2010
comment
C++0x имеет std::to_string(), который преобразует любой числовой тип в строку. g++ поддерживает его (вместе с std::stoi(), std::stol() и т. д. для обратного) - person Cubbi; 29.07.2010
comment
@Cubbi: Теперь, когда я не знал. Спасибо! - person Justin Ardini; 29.07.2010

Аналогично, в моем текущем проекте у нас есть файл с именем: stlutils.h, который содержит некоторые методы, например, contains(). Реализовано как:

template<class Container, class T>
bool contains(const Container& c, const T& value) {
   return std::find(c.begin(), c.end(), value) != c.end();
}

Функций больше, но думаю вы поняли

person Edison Gustavo Muenz    schedule 29.07.2010

В других ответах говорилось, что вы можете написать служебные функции, чтобы сделать это за вас, и это хорошая идея, где вам это нужно. Но я решил указать на важный момент: STL разработан с учетом алгоритмической эффективности. Почти все операции с STL имеют стандартные требования к эффективности big-O.

Если бы у vector было contains() члена, это, безусловно, было бы O(n) для вызова, так как вектор — это простой непрерывный список. Поскольку это также удобно, это может побудить программистов использовать его регулярно, даже для больших наборов данных, поощряя разработку приложений с низкой алгоритмической производительностью. В случае contains(), если важно проверить, содержит ли контейнер определенный элемент, с дополнительной гарантией того, что все элементы уникальны, std::set почти наверняка будет лучшим выбором, с эффективностью поиска O(log n) или даже O(1) для std::unordered_set.

Итак, мое личное мнение: изучите все контейнеры и функции, которые предлагает STL, и вы обнаружите, что хотя это кратко, это способствует более эффективному стилю программирования. Вы спрашиваете в вопросе, не упускаете ли вы что-то, и я бы сказал да — вы хотите более тщательно подумать о контейнере, который используете. В последнее время я регулярно использую set вместо vector.

person AshleysBrain    schedule 29.07.2010

Я не большой поклонник оберток, но если они вам помогут, дерзайте. Я думаю, со временем вы обнаружите, что вам захочется использовать свою служебную функцию с другими контейнерами, помимо std::vector. В конце концов ваша служебная функция становится настолько универсальной, что вы можете использовать std::find напрямую.

Но уверены ли вы, что используете правильный контейнер? std::set имеет метод count(), который по существу эквивалентен contains(). И это O (log (n)), а не O (n).

person Ben    schedule 04.08.2010