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. Но я не уверен, если он соответствует стандарту.
Ответы
Ответ 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), объект будет оставаться в любом случае до тех пор, пока не будет уничтожен его собственной областью. С его исходными данными или без них.