Чому std :: set не має функції "містить" член?


103

Я активно використовую std::set<int>і часто мені просто потрібно перевірити, чи містить такий набір число чи ні.

Мені було б природно писати:

if (myset.contains(number))
   ...

Але через відсутність containsчлена мені потрібно написати громіздкий:

if (myset.find(number) != myset.end())
  ..

або не так очевидно:

if (myset.count(element) > 0) 
  ..

Чи є причина такого дизайнерського рішення?


7
Більшість стандартних бібліотек працює з ітераторами, тому зазвичай функція повернення ітераторів - це те, що ви очікували. Не важко, хоч написати функцію, щоб її абстрагувати. Швидше за все, компілятор буде вбудовувати його, оскільки це повинен бути лише рядок або 2 коду, і ви отримаєте ту ж продуктивність.
NathanOliver

3
Ще одна (більш фундаментальна) проблема count()підходу полягає в тому, що він робить більше роботи, ніж countains()повинен був би зробити.
Лев Хайнсаар

11
Основна причина позаду цього проекту рішення є те , contains()що повертає boolб втратити цінну інформацію про те, де елемент знаходиться в колекції . find()зберігає та повертає цю інформацію у вигляді ітератора, тому є кращим вибором для такої загальної бібліотеки, як STL. (Це не означає, що bool contains()це не дуже приємне або навіть необхідне.)
Лео Хайнсаар

3
Створити contains(set, element)безкоштовну функцію легко, використовуючи загальнодоступний інтерфейс набору. Тому інтерфейс набору функціонально повний; додавання зручного методу просто збільшує інтерфейс, не вмикаючи жодної додаткової функції, що не є способом C ++.
Toby Speight

3
Ми закриваємо все в ці дні? Як це питання в першу чергу "засноване на думці"?
Містер

Відповіді:


148

Я думаю, що це було, мабуть, тому, що вони намагалися зробити std::setі std::multisetмаксимально схоже. (І, очевидно, countмає цілком розумне значення для std::multiset.)

Особисто я вважаю, що це була помилка.

Це виглядає не так вже й погано, якщо ви робите вигляд, що countце просто помилка написання containsі пишете тест так:

if (myset.count(element)) 
   ...

Це все-таки соромно.


5
До речі, це точно так само з картами та мультимапами (що так само негарно, але менш потворно, ніж усі ці порівняння .end()).
Маттео Італія

8
Крім того, вони, можливо, не бачили потреби в додатковому члені contains()на тій підставі, що це було б зайвим, оскільки для будь-якого std::set<T> sі T tрезультат результат s.contains(t)точно ідентичний результату static_cast<bool>(s.count(t)). Оскільки використання значення в умовному виразі імпліцитно покладене на нього bool, можливо, вони вважають, що count()достатньо добре відповідають цілі.
Час Джастіна - Поновіть Моніку

2
Неправильне написання? if (myset.ICanHaz(element)) ...: D
Стефан Гурішон

3
@MartinBonner Насправді не важливо, чи були причини його відсутності тупими. Це також не має особливого значення, якщо розмова не була стовідсотковою мотивацією. Ваша відповідь тут - лише розумна здогадка про те, як ви вважаєте, що це має бути. Бесіда та відповідь від когось, хто не лише в ній брав участь, але й покладав завдання запропонувати її (навіть якщо вони цього не зробили), неминуче наближений до істини, ніж ця здогадка, як би ви не дивилися на неї. Як мінімум, вам слід принаймні згадати це у цій відповіді, це було б великим вдосконаленням і було б відповідальним.
Jason C

2
@JasonC: Чи можете ви продовжити та відредагувати розділ внизу, будь ласка? Я не дуже розумію точку, яку ви намагаєтеся зробити, і коментарі, ймовірно, не найкращий спосіб пояснити це. Дякую!
Мартін Боннер підтримує Моніку

44

Щоб мати змогу писати if (s.contains()), contains()треба повернути bool(або тип bool, який можна конвертувати , що вже інша історія), як binary_searchі.

Основна причина позаду дизайнерського рішення НЕ робити це таким чином, що , contains()який повертає boolб втратити цінну інформацію про те, де елемент знаходиться в колекції . find()зберігає та повертає цю інформацію у вигляді ітератора, тому є кращим вибором для такої загальної бібліотеки, як STL. Це завжди було керівним принципом для Алекса Степанова, як він часто пояснював (наприклад, тут ).

Щодо count()підходу в цілому, хоча це часто нормальне вирішення, проблема з ним полягає в тому, що він робить більше роботи, ніж contains() доводиться робити .

Це не означає, що bool contains()це не дуже приємне або навіть необхідне. Нещодавно ми обговорили цю саму проблему в групі стандартів ISO C ++ - майбутні пропозиції.


5
І цікаво відзначити, що ця дискусія закінчилася майже єдиною думкою щодо того, що вона є бажаною, і вас просять написати пропозицію.
PJTraill

@PJTraill True, і причина, що я не пішов вперед, полягає в тому, contains()що, очевидно, сильно взаємодіятимуть із існуючими контейнерами та алгоритмами, на які сильно впливатимуть концепції та діапазони - на час, який, як очікується, ввійде в C ++ 17 - і Я був переконаний (в результаті дискусії, а також пари приватних обмінів електронною поштою), що краще зачекати їх спочатку. Звичайно, у 2015 році було не ясно, що ні поняття, ні діапазони не перетворять їх на C ++ 17 (насправді, були великі надії, що вони будуть). Я не впевнений, що варто це переслідувати зараз.
Лев Хайнсаар

1
Тому що std::set(про що йдеться у запитанні), я не бачу, як countпрацює більше, ніж containsпотрібно було б зробити. Реалізація glibc count(приблизно) return find(value) == end() ? 0 : 1;. Окрім подробиць про тернарний оператор проти просто повернення != end()(яке я очікую, що оптимізатор видалить), я не можу зрозуміти, як відбувається ще робота.
Мартін Боннер підтримує Моніку

4
"... містить (), який повертає bool, втрачає цінну інформацію про те, де знаходиться елемент у колекції " - Якщо користувач дзвонить myset.contains()(якщо він існував), це було б досить сильним свідченням того, що ця інформація не є цінною ( користувачеві в цьому контексті).
Кіт Томпсон

1
Чому робиться count()більше роботи, ніж contains()потрібно було б зробити std::set? Це унікально, тому count()може бути саме таким, return contains(x) ? 1 : 0;який точно такий же.
Timmmm

22

Цього не вистачає, тому що ніхто не додав. Ніхто не додав його, тому що контейнери з 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)) {
}

В основному, ви можете написати методи розширення для більшості stdтипів C ++, використовуючи цю техніку.

Має набагато більше сенсу просто зробити це:

if (some_set.count(some_element)) {
}

але мене розважає метод розширення.

Дійсно сумно, що ефективне написання containsмогло б бути швидшим на multimapабо multiset, оскільки вони просто повинні знайти один елемент, тоді countяк повинні знайти кожен з них і порахувати їх .

Мультизапис, що містить 1 мільярд примірників із 7 (ви знаєте, якщо ви закінчитеся) може мати дуже повільний .count(7), але може мати дуже швидкий contains(7).

За допомогою описаного вище способу розширення ми могли би зробити це швидше для цього випадку, використовуючи lower_bound, порівнюючи end, а потім порівнюючи елемент. Однак для цього як для невпорядкованого мяу, так і для замовленого мяу вимагається фантазія SFINAE або специфічних перевантажень для контейнерів.


2
1 мільярд примірників 7? І тут я подумав, що std::setне може містити дублікатів і тому std::set::countзавжди повертається 0або 1.
nwp

5
@nwp std::multiset::countможе
milleniumbug

2
@nwp Мій недолік backticksнавколо слова "встановити" полягає в тому, що я не маю на увазі std::setконкретно. Щоб ви почували себе краще, я додам мульти
Якк - Адам Невраумон

3
Мені здається, не вистачає жарту про те, що б "мяу" мала бути посиланням.
user2357112 підтримує Monica

2
@ user2357112 meow - це заповнювач для "встановити чи карту". Запитайте у STL чому.
Якк - Адам Невраумон

12

Ви дивитесь на конкретний випадок і не бачите більшої картини. Як зазначено в документації, std::set відповідає вимозі концепції AssociativeContainer . Для цієї концепції не має жодного сенсу мати containsметод, оскільки це в значній мірі марний std::multisetі std::multimap, але countпрацює чудово для всіх. Хоча метод containsможе бути доданий в якості псевдоніма countдля std::set, std::mapі їх хеш - версії (як lengthдля size()в std::string), але виглядає як бібліотеки творці не бачили реальну потребу в ньому.


8
Зауважте, що stringце монстр: він існував до STL, де він мав lengthі всі ті методи, які базуються на індексах, а потім був "підтверджений", щоб вписатись у модель STL ... без вилучення існуючих методів з причин відсталої сумісності . Див. GotW # 84: Monoliths Unstrung => stringдійсно порушує принцип дизайну "мінімальної кількості функцій членів".
Матьє М.

5
Але тоді стає питання "Чому варто мати таку концепцію AssociativeContainer?" - і я не впевнений, що це було заднім числом.
Мартін Боннер підтримує Моніку

24
Питання, чи містить мультисети, мультимапа або карта, має для мене ідеальний сенс. Насправді, containsза рівнем зусиль набір / карта, але його можна зробити швидше, ніж countна мультисеті / мультимапі.
Якк - Адам Невраумон

5
AssociativeContainer не вимагає, щоб класи не мали containsметоду.
user2357112 підтримує Monica

6
@Slava Це як би сказати size()і empty()копії, але багато контейнерів мають і те, і інше.
Баррі

10

Хоча я не знаю, чому 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)) ...

3
-1, оскільки це прямо суперечить тому, що насправді containsметод існує, його просто називають дурно.
Маттео Італія

4
"STL прагне запропонувати мінімальний інтерфейс" холодного std::string кашлю
болов

6
@bolov: ваша думка? std.::stringНЕ є частиною STL! Це частина стандартної бібліотеки і була заздалегідь шаблонна ...
MFH

3
@MatteoItalia countможе бути повільнішим, оскільки йому ефективно потрібно зробити два findс, щоб отримати початок і кінець діапазону, якщо кодом надано спільний доступ multiset.
Марк Викуп

2
ОП вже знає, що це зайве, але, мабуть, хоче, щоб код чітко читав contains. Я не бачу в цьому нічого поганого. @MarkRansom маленька SFINAE - запобігти прив'язці цього шаблону до речей, які він не повинен.
рустика

7

Справжня причина для 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();
}

Звичайно, все це є простою міркуванням.


1
Так, але для наборів ints це не стосується.
Jabberwocky

7
За винятком того, що люди можуть просто писати if (myMap.count("Meaning of universe"))просто чудово, так ...?
Баррі

@MichaelWalz На жаль, ви праві. Я змінив свою відповідь, щоб включити також заданий приклад. Однак міркування набору інтів є для мене загадкою.
Мартін Дроздик

2
Це не може бути правильним. Вони так само легко можуть написати ваш неефективний код, containsяк і з count.
Мартін Боннер підтримує Моніку



0

містить () повинен повернути bool. Використовуючи компілятор C ++ 20, я отримую наступний вихід для коду:

#include<iostream>
#include<map>
using namespace std;

int main()
{
    multimap<char,int>mulmap;
    mulmap.insert(make_pair('a', 1)); //multiple similar key
    mulmap.insert(make_pair('a', 2)); //multiple similar key
    mulmap.insert(make_pair('a', 3)); //multiple similar key
    mulmap.insert(make_pair('b', 3));
    mulmap.insert({'a',4});
    mulmap.insert(pair<char,int>('a', 4));
    
    cout<<mulmap.contains('c')<<endl;  //Output:0 as it doesn't exist
    cout<<mulmap.contains('b')<<endl;  //Output:1 as it exist
}

-1

Ще одна причина полягає в тому, що це створило б програмісту помилкове враження, що std :: set є набором у сенсі теорії математичних множин. Якщо вони реалізують це, то випливає багато інших питань: якщо std :: set містить () для значення, чому він не має його для іншого набору? Де знаходяться об'єднання (), перетин () та інші задані операції та предикати?

Відповідь, звичайно, полягає в тому, що деякі задані операції вже реалізовані як функції в (std :: set_union () і т.д.), а інші такі ж тривіально реалізовані, як і містить (). Функції та об'єкти функціонування краще працюють з математичними абстракціями, ніж члени об'єкта, і вони не обмежуються певним типом контейнера.

Якщо потрібно реалізувати повний функціонал математики, у нього є не лише вибір базового контейнера, але і він має вибір деталей реалізації, наприклад, чи буде функція його теорії_union () працювати з незмінними об'єктами, більш підходящими для функціонального програмування чи це змінило б операнди та зберегло пам'ять? Чи буде вона реалізована як об’єкт функції з самого початку, або було б краще реалізувати це C-функція, а при необхідності використовувати std :: function <>?

Як і зараз, std :: set - це просто контейнер, добре підходить для реалізації набору в математичному сенсі, але він майже далекий від теоретичного набору, оскільки std :: вектор від теоретичного вектора.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.