Почему std:: set не имеет функции-члена "contains"?
я сильно использую std::set<int>
и часто мне просто нужно проверить, содержит ли такой набор число или нет.
Я бы счел естественным написать:
if (myset.contains(number))
...
но из-за отсутствия contains
член, мне нужно написать громоздкий:
if (myset.find(number) != myset.end())
..
или не так очевидно:
if (myset.count(element) > 0)
..
есть ли причина для этого проектного решения ?
7 ответов
Я думаю, это было, вероятно, потому, что они пытались сделать std::set
и std::multiset
как можно более похожими. (И, очевидно,count
имеет совершенно разумное значение для std::multiset
.)
лично я думаю, что это было ошибкой.
это выглядит не так уж плохо, если вы притворяетесь, что count
- это просто ошибочное contains
и напишите тест как:
if (myset.count(element))
...
это все еще позор, хотя.
чтобы иметь возможность писать if (s.contains())
, contains()
должен возвратить bool
(или тип, конвертируемый в bool
, что другая история), как binary_search
делает.
на фундаментальная причина за проектным решением не сделать это таким образом, что contains()
возвращает bool
б потерять ценную информацию о том, где элемент находится в коллекции. find()
сохраняет и возвращает эту информацию в виде итератора, поэтому лучший выбор для универсальной библиотеки, такой как STL. Это всегда было руководящим принципом для Алексея Степанова, как он часто объяснял (например,здесь).
как к count()
подход в целом, хотя это часто окей обходной путь, проблема в том, что он делает больше работы, чем contains()
придется делать.
это не значит, что a bool contains()
не очень приятно иметь или даже необходимый. Некоторое время назад у нас был долгая дискуссия об этом же вопросе в
Стандарт ISO C++ - Группа будущих предложений.
его не хватает, потому что никто его не добавил. Никто не добавил его, потому что контейнеры из STL, что std
библиотека включена, где предназначена для минимального интерфейса. (Обратите внимание, что std::string
не пришел из STL таким же образом).
если вы не возражаете против какого-то странного синтаксиса, вы можете подделать его:
template<class K>
struct contains_t {
K&& k;
template<class C>
friend bool operator->*( C&& c, contains_t&& ) {
auto range = std::forward<C>(c).equal_range(std::forward<K>(k));
return range.first != range.second;
// faster than:
// return std::forward<C>(c).count( std::forward<K>(k) ) != 0;
// for multi-meows with lots of duplicates
}
};
template<class K>
containts_t<K> contains( K&& k ) {
return {std::forward<K>(k)};
}
использование:
if (some_set->*contains(some_element)) {
}
в основном, вы можете написать методы расширения для большинства C++ std
типы с помощью этого метод.
это имеет гораздо больше смысла, чтобы просто сделать это:
if (some_set.count(some_element)) {
}
но меня забавляет метод метода расширения.
действительно грустно, что написание эффективного contains
может быть быстрее на multimap
или multiset
, так как они просто должны найти один элемент, а count
должен найти каждый из них и считать их.
мультисет, содержащий 1 миллиард копий 7 (вы знаете, в случае, если вы закончите) может иметь действительно медленно .count(7)
, но может иметь очень быстрый contains(7)
.
С помощью вышеуказанного метода расширения мы могли бы сделать это быстрее для этого случая, используя lower_bound
по сравнению с end
, а затем по сравнению с элементом. Однако это для неупорядоченного мяу, а также заказанного мяу потребует причудливых SFINAE или перегрузок контейнера.
вы смотрите в конкретный случай и не видите большую картину. Как указано в документация std::set
соответствует требованиям AssociativeContainer концепции. Для этой концепции нет никакого смысла иметь contains
метод, так как он практически бесполезен для std::multiset
и std::multimap
, а count
работает для всех из них. Хотя метод contains
может быть добавлен в качестве псевдонима для count
на std::set
, std::map
и их хэшированные версии (например,length
для size()
на std::string
), но, похоже, создатели библиотеки не видели в этом реальной необходимости.
хотя я не знаю, почему std::set
нет contains
но count
который только когда-либо возвращает 0
или 1
,
вы можете написать шаблонный contains
вспомогательная функция, как это:
template<class Container, class T>
auto contains(const Container& v, const T& x)
-> decltype(v.find(x) != v.end())
{
return v.find(x) != v.end();
}
и используйте его так:
if (contains(myset, element)) ...
истинная причина set
для меня загадка, но одно возможное объяснение этой же конструкции в map
может быть, чтобы люди не писали неэффективный код случайно:
if (myMap.contains("Meaning of universe"))
{
myMap["Meaning of universe"] = 42;
}
что привело бы к двум map
поиск.
вместо этого вы вынуждены получить итератор. Это дает вам мысленный намек, что вы должны повторно использовать итератор:
auto position = myMap.find("Meaning of universe");
if (position != myMap.cend())
{
position->second = 42;
}
, который потребляет только один map
поиск.
когда мы осознаем, это set
и map
сделаны из той же плоти, мы можем применить этот принцип к set
. То есть, если мы хотим действовать по пункту в set
только если он присутствует в set
, этот дизайн может помешать нам писать код следующим образом:
struct Dog
{
std::string name;
void bark();
}
operator <(Dog left, Dog right)
{
return left.name < right.name;
}
std::set<Dog> dogs;
...
if (dogs.contain("Husky"))
{
dogs.find("Husky")->bark();
}
конечно, все это только домыслы.
другая причина заключается в том, что это даст программисту ложное впечатление, что std::set-это множество в смысле теории математических множеств. Если они реализуют это, то последует много других вопросов: если std::set имеет contains() для значения, почему он не имеет его для другого набора? Где находятся union (), intersection () и другие операции и предикаты множества?
ответ, конечно, заключается в том, что некоторые из заданных операций уже реализованы как функции в (std::set_union() и т. д.) а другие так же тривиально реализованы, как contains (). Функции и объекты функций лучше работают с математическими абстракциями, чем члены объектов, и они не ограничены конкретным типом контейнера.
Если нужно реализовать полную функциональность математического набора, у него есть не только выбор базового контейнера, но и выбор деталей реализации, например, будет ли его функция theory_union() работать с неизменяемыми объектами, лучше подходящими для функционального программирования, или это будет изменить его операнды и сохранить память? Будет ли он реализован как объект функции с самого начала или лучше реализовать C-функцию и использовать std::function при необходимости?
Как и сейчас, std::set-это просто контейнер, хорошо подходящий для реализации set в математическом смысле, но он почти так же далек от теоретического множества, как std:: vector от теоретического вектора.