Я находился в процессе выбора одного из двух способов помещения вещей в unordered_map:
std::unordered_map<Key, Value> map;
map.emplace(
std::piecewise_construct,
std::forward_as_tuple(a),
std::forward_as_tuple(b, c, d));
против
std::unordered_map<Key, DifferentValue> map;
auto& value = map[a];
if (value.isDefaultInitialized())
value = DifferentValue(b, c, d);
Я провел несколько экспериментов, чтобы увидеть, какой из них будет работать лучше, чтобы обнаружить, что при вставке уникальных элементов поведение (как в эффективности) было в основном эквивалентным.
Тем не менее, в случае вставки дублирующих элементов и с учетом того, что построение Value или DifferentValue нетривиально, я с удивлением обнаружил, что emplace создает объект независимо от того, вставит ли он его или нет.
Таким образом, второй метод, похоже, выиграл в этом случае, так как конструктор по умолчанию просто содержит isDefaultInitialized_ (true) и не намного больше.
Для emplace код выглядит так:
... _M_emplace(std::true_type, _Args&&... __args) {
__node_type* __node = _M_allocate_node(std::forward<_Args>(__args)...);
const key_type& __k = this->_M_extract()(__node->_M_v);
...
if (__node_type* __p = _M_find_node(__bkt, __k, __code)) {
_M_deallocate_node(__node);
return std::make_pair(iterator(__p), false);
}
return std::make_pair(_M_insert_unique_node(__bkt, __code, __node), true);
}
Итак, хотя я собираюсь перейти ко второму методу (даже если он требует назначения перемещения и конструкторов перемещения и дополнительных полей), мне было интересно, есть ли хорошее обоснование того, почему emplace создает объект, который он позже игнорирует? То есть должен ли он сначала проверить, нужно ли ему создать объект, и заблаговременно, если он уже существует?
(обратите внимание, что для моего конкретного случая инициализированные элементы по умолчанию не считаются действительными, поэтому вопрос действительно только об emplace)
Для записи я нашел что-то под 23.2.4 таблицы 102:
Effects: Inserts a value_type object t constructed with std::forward<Args>(args)...
if and only if there is no element in the container with key equivalent to the
key of t.
который, я думаю, позволил бы не создавать объект.
На мой взгляд, цитируемая часть стандарта вводит в заблуждение, поскольку предполагает, что объект создается только в том случае, если в контейнере нет соответствующего элемента. Я предполагаю, что они пытаются заявить:
Последствия: Создает
value_type
объектt
сstd::forward<Args>(args)...
, Вставляет построенный объектt
если и только если в контейнере нет такого элемента с ключом, эквивалентным ключуt
,
Причина заключается в следующем: реализация функции emplace
должен построить t
чтобы узнать, существует ли элемент с эквивалентным ключом, потому что реализация должна вызвать гашиш функция и равняется сказуемое. Однако в целом они могут быть вызваны только с объектами типа value_type
, не с кортежи используется для построения этих объектов.
Теоретически можно было бы указать устанавливать функция, которая не создает t
если уже есть элемент с эквивалентным ключом. Интересно, что нечто подобное будет добавлено в C ++ 14 для std::map::find
, Смотрите следующую документацию:
Существует две перегрузки, которые можно использовать с произвольными типами, если только сравнить Функция выполняет некоторые дополнительные требования. Интересно, что такой перегрузки для std::unordered_map
,