Sunday, 11 June 2017

c++ - std::unordered_map::emplace object creation



I was in the process of selecting one of two methods of putting things into an unordered_map:



std::unordered_map map;
map.emplace(
std::piecewise_construct,
std::forward_as_tuple(a),
std::forward_as_tuple(b, c, d));


vs



std::unordered_map map;
auto& value = map[a];
if (value.isDefaultInitialized())
value = DifferentValue(b, c, d);


I did some experiments to see which one would perform better to find that when inserting unique elements, the behaviour (as in efficiency) was basically equivalent.



However, in the case of inserting duplicate items, and consider that the construction of Value or DifferentValue is not trivial, I was surprised to find is that emplace constructs the object regardless of whether it will insert it or not.



So, the second method seems to win by far in that case since the default constructor just has isDefaultInitialized_(true) in there and not much more.



For emplace, the code seems to be:



... _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);
}


So, although I'm going to go with the second method (even if it requires move assignment and move constructors and extra fields), I was wondering is there a good rationale for why emplace creates an object that it later disregards? That is, should it first check if it needs to create the object and early out if it already exists?



(note that for my particular case default initialized items are not considered valid, so the question is really just about emplace)



For the record, I found something under 23.2.4 table 102:



Effects: Inserts a value_type object t constructed with std::forward(args)...
if and only if there is no element in the container with key equivalent to the
key of t.


which I think would allow for not creating the object.


Answer



In my opinion, the quoted part from the standard is misleading, because it suggests, that the object is only constructed if there is no matching element in the container. I guess they are trying to state:




Effects: Constructs a value_type object t with std::forward(args).... Inserts the constructed object t if and only if there is no such element in the container with key equivalent to the key of t.




The reason is: The implementation of the function emplace has to construct t in order to find out if an element with an equivalent key exists, because the implementation has to invoke the hash function and the equals predicate. However, in general they can only be invoked with objects of type value_type, not with tuples used to construct these objects.



In theory, it would be possible to specify an emplace function, that doesn't construct t if there is already an element with an equivalent key. Interestingly, something similar will be added with C++14 for std::map::find. See the following documentation:





There are two overloads, that can be used with arbitrary types, as long as the compare function fulfills some additional requirements. Interestingly enough, there is no such overload for std::unordered_map.


No comments:

Post a Comment

c++ - Does curly brackets matter for empty constructor?

Those brackets declare an empty, inline constructor. In that case, with them, the constructor does exist, it merely does nothing more than t...