Гарантирует ли стандарт C++, что неудачная вставка в ассоциативный контейнер не изменит аргумент rvalue-reference?
#include <set>
#include <string>
#include <cassert>
using namespace std::literals;
int main()
{
auto coll = std::set{ "hello"s };
auto s = "hello"s;
coll.insert(std::move(s));
assert("hello"s == s); // Always OK?
}
Гарантирует ли стандарт C++, что неудачная вставка в ассоциативный контейнер не изменит аргумент rvalue-reference?
Ответы
Ответ 1
Явный и однозначный НЕТ. Стандарт не имеет этой гарантии, и поэтому существует try_emplace.
Смотрите примечания:
В отличие от insert или emplace, эти функции не перемещаются из rvalue аргументы, если вставка не происходит, что облегчает манипулировать картами, значения которых являются типами только для перемещения, такими как std::map<std::string, std::unique_ptr<foo>>
. Кроме того, try_emplace
обрабатывает ключ и аргументы mapped_type отдельно, в отличие от emplace
, который требует аргументов для построения value_type
(что есть, std::pair
)
Ответ 2
Номер
Хотя @NathanOliver указывает, что элемент не будет вставлен в том и только в том случае, если нет эквивалентного ключа, это не гарантирует, что аргументы не будут изменены.
Фактически, [map.modifiers] говорит следующее
template <class P>
pair<iterator, bool> insert(P&& x);
эквивалентно return emplace(std::forward<P>(x)).
Где emplace
может идеально передать аргументы для создания другого P
, оставив x
в каком-то действительном, но неопределенном состоянии.
Вот пример, который также демонстрирует (не доказывает), что с std::map
(ассоциативный контейнер) значение немного перемещается:
#include <iostream>
#include <utility>
#include <string>
#include <map>
struct my_class
{
my_class() = default;
my_class(my_class&& other)
{
std::cout << "move constructing my_class\n";
val = other.val;
}
my_class(const my_class& other)
{
std::cout << "copy constructing my_class\n";
val = other.val;
}
my_class& operator=(const my_class& other)
{
std::cout << "copy assigning my_class\n";
val = other.val;
return *this;
}
my_class& operator=(my_class& other)
{
std::cout << "move assigning my_class\n";
val = other.val;
return *this;
}
bool operator<(const my_class& other) const
{
return val < other.val;
}
int val = 0;
};
int main()
{
std::map<my_class, int> my_map;
my_class a;
my_map[a] = 1;
std::pair<my_class, int> b = std::make_pair(my_class{}, 2);
my_map.insert(std::move(b)); // will print that the move ctor was called
}
Ответ 3
(Ответ только для С++ 17)
Я считаю, что правильный ответ находится где-то между ответом NathanOliver (теперь удаленным) и ответом AndyG.
Как указывает AndyG, такая гарантия вообще не может существовать: иногда библиотека должна фактически выполнить конструкцию перемещения только для того, чтобы определить, возможна ли вставка. Это будет иметь место для функции emplace
, поведение которой определяется стандартом как:
Эффекты: Вставляет объект value_type
t
, созданный с помощью std::forward<Args>(args)...
, тогда и только тогда, когда в контейнере нет элемента с ключом, эквивалентным ключу t
.
Мы можем интерпретировать это как утверждение, что объект t
создается независимо от чего, а затем удаляется, если вставка не может произойти, потому что значение t
или t.first
уже существует в наборе или карте, соответственно. И поскольку метод template <class P> pair<iterator, bool> insert(P&&)
из std::map
определен в терминах emplace
, как указывает AndyG, он имеет такое же поведение. Как указывает SergeyA, методы try_emplace
предназначены для избежания этой проблемы.
Однако в конкретном примере, заданном OP, вставляемое значение имеет точно такой же тип, что и тип значения контейнера. Поведение такого вызова insert
определяется параграфом общих требований, ранее заданным NathanOliver:
Эффекты: Вставляет t
тогда и только тогда, когда в контейнере нет элемента с ключом, эквивалентным ключу t
.
В этом случае не предоставляется лицензии для библиотеки на изменение аргумента в случае, когда вставка не происходит. Я полагаю, что вызов библиотечной функции не должен иметь каких-либо наблюдаемых побочных эффектов, кроме того, что явно разрешено стандартом. Таким образом, этот случай, t
не должен быть изменен.