Std:: unordered_set <T>:: insert (T &&): аргумент перемещается, если он существует - программирование
Подтвердить что ты не робот

Std:: unordered_set <T>:: insert (T &&): аргумент перемещается, если он существует

Этот вопрос касается спецификации нескольких функций в стандартной библиотеке С++ 11, которые принимают свои аргументы как ссылки rvalue, но не потребляют их во всех случаях. Одним из примеров является std::unordered_set<T>::insert(T&&).

Довольно ясно, что этот метод будет использовать конструктор перемещения T для построения элемента внутри контейнера, если он еще не существует. Однако что произойдет, если элемент уже существует в контейнере? Я уверен, что нет причин для изменения объекта в случае. Тем не менее, я не нашел ничего в стандарте С++ 11, поддерживающем мое утверждение.

Вот пример, чтобы показать, почему это может быть интересно. Следующий код считывает строки из std:: cin и удаляет первое появление дубликатов строк.

std::unordered_set<std::string> seen;
std::string line;
while (getline(std::cin, line)) {
    bool inserted = seen.insert(std::move(line)).second;
    if (!inserted) {
        /* Is it safe to use line here, i.e. can I assume that the
         * insert operation hasn't changed the string object, because 
         * the string already exists, so there is no need to consume it. */
        std::cout << line << '\n';
    }
}

По-видимому, этот пример работает с GCC 4.7. Но я не уверен, если он соответствует стандарту.

4b9b3361

Ответ 1

Я нашел это примечание в стандарте (17.4.6.9):

[Примечание. Если программа передает значение lvalue в значение x при передаче этого lvalue в библиотечную функцию (например, вызывая функцию с аргументом move(x)), программа эффективно запрашивает эту функцию для обработки этого lvalue как временный характер. Реализация бесплатна для оптимизации проверок псевдонимов, которые могут потребоваться, если аргумент был lvalue. - конечная нота]

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

Ответ 2

Семантика, заданная для insert в неупорядоченных ассоциативных контейнерах в п. 23.2.5/Таблица 103, не определяет, будет ли конструктор перемещения аргумента insert вызываться, если вставка терпит неудачу, в формулировке говорится только о происходит ли вставка:

a_uniq.insert(t)

Возвращает: pair<iterator, bool>

Требуется: если t является неконстантным выражение rvalue, t должно быть MoveInsertable в X; в противном случае tдолжен быть CopyInsertable в X.

Эффекты: Вставляет t тогда и только тогда, когда в контейнере нет элемента с ключом, эквивалентным ключу t. Компонент bool возвращенной пары указывает, является ли вставка происходит, а компонент итератора указывает на элемент с ключом, эквивалентным ключу t.

<ы > Однако спецификация для emplace более понятная (также из таблицы 103), и вы можете использовать ее вместо insert, чтобы получить необходимые гарантии:

a_uniq.emplace(args)

Возвращает: pair<iterator, bool>

Требуется: T должно быть EmplaceConstructible в X из args.

Эффекты: Вставляет объект t tпостроенный с помощью std:: forward (args)... тогда и только тогда, когда есть в контейнере нет элемента с ключом, эквивалентным ключу t. bool-компонента возвращаемой пары истинна тогда и только тогда, когда происходит вставка, а итераторная компонента парных точек к элементу с ключом, эквивалентным ключу t.

Я интерпретирую это как означающий ((Вставляет объект t объект t построенный...) (тогда и только тогда, когда в контейнере нет элемента...)), т.е. как вставка, так и построение должны только произойдет, если в контейнере нет соответствующего элемента. Если объект не сконструирован, std::string, который вы передаете, никогда не будет передан конструктору перемещения и, следовательно, будет по-прежнему действительным после неудачного вызова emplace.

gcc 4.7.0, похоже, не поддерживает unordered_set::emplace, но он находится в стандарте (§23.5.6.1)

Как отметил @NicolBolas в комментариях, и, несмотря на вышеизложенное, невозможно реализовать функцию emplace, которая не создает t, если конфликтующая запись уже существует.

Таким образом, единственный способ получить семантику, которую вы хотите, в соответствии со стандартами, - это сделать find, за которым следует условно insert или emplace.

Ответ 3

Это правильно.

Конечно, когда речь идет о философии, все может быть поставлено под сомнение, но компилятор должен как-то сделать.

Выбор дизайнеров заключался в том, что для выполнения движения необходимо место. Если такого места нет, переход не происходит.

Обратите внимание, что любая функция принимает & &, предполагается, что параметр является "временным": если он не "украл" данные, временное будет уничтожено в конце выражения. Если временность была принудительно (через std:: move), объект будет оставаться в любом случае до тех пор, пока не будет уничтожен его собственной областью. С его исходными данными или без них.