Behaviour of map emplace vs try_emplace?

Viewed 1440

Let's say I have an unordered_map:

std::unordered_map<Key, BigObject> big_objects;

where BigObject is like:

struct BigObject {
    BigObject(P1, P2, P3); // <--- expensive
    /*...*/
};

and I have a Key k, and a set of arguments for BigObjects constructor P1 p1, P2 p2 and P3 p3.

If big_objects already contains k I want to do nothing. If big_objects does not contain K I want to construct and insert BigObject(p1,p2,p3)

Is it possible to do this with a single lookup on big_objects?

ie Will emplace or try_emplace construct BigObject if the key is already present?

Update

After some testing it seems like emplace does construct BigObject but try_emplace doesn't ?

ie

big_objects[k] = BigObject(p1,p2,p3) // insert k, k now present...

big_objects.emplace(k, p1, p2, p3); // does construct a BigObject

big_objects.try_emplace(k, p1, p2, p3); // does not construct a BigObject

Is this correct?

2 Answers

Yes, it is correct, this is the reason why try_emplace exists, it doesn't touch its arguments if the key is already present.

Cppreference try_emplace

Unlike insert or emplace, these functions do not move from rvalue arguments if the insertion does not happen, which makes it easy to manipulate maps whose values are move-only types, such as std::map<std::string, std::unique_ptr>. In addition, try_emplace treats the key and the arguments to the mapped_type separately, unlike emplace, which requires the arguments to construct a value_type (that is, a std::pair)

As a bonus point, you do not have to use the ugly std::piecewise_construct tag to pass both the key and the value constructor arguments ( unless you want to inplace construct the key too).

Yes, like this algorithm problem: enter image description here

One solution is:

int findShortestSubArray(vector<int>& nums) 
{
    std::unordered_map<int, std::tuple<int, int, int>> map;
    int s = nums.size();
    for(int i = 0; i < s; ++i)
    {
        if(!map.try_emplace(nums.at(i), std::tuple<int, int, int>{1,i,i}).second)
        {
            ++std::get<0>(map.at(nums.at(i)));
            std::get<2>(map.at(nums.at(i))) = i;
        }                
    }
    //...
}

This solution takes the result of its execution from the return value of try_emplace() and uses it as a condition for an if statement, end up with a very nice space-time overhead:

Execution Time: 32ms, beating 97.50% of users in all C++ commits
Memory consumption: 24.3MB, beating 93.47% of users in all C++ commits

but if I replace try_emplace() with emplace(), Space-time costs will balloon:

Execution Time: 84ms, beating 61.24% of users in all C++ commits
Memory consumption: 33.8MB, beating 6.65% of users in all C++ commits

This reason has been answered by other friends, and I think that's a good example of the difference between the two.

Related