c++c++11emplace

How best to use emplace with std::map


I was experimenting with using std::map::emplace() instead of insert. I have a simple test program below:

#include <iostream>
#include <map>
#include <string>

class CTest
{
public:
    CTest() : Value(0), Name() { std::cout << "Default constructor" << std::endl; };
    CTest(int val, const std::string &name) : Value(val), Name(name) {
        std::cout << "Parameterized constructor" << std::endl; }
    CTest(const CTest& test) {
        Value = test.Value; Name = test.Name;  std::cout << "Copy Constructor" << std::endl; }
    CTest(CTest&& test) noexcept {
        Value = test.Value; Name = test.Name; std::cout << "Move Constructor" << std::endl; }
    CTest& operator=(const CTest& test) {
        Value = test.Value; Name = test.Name; std::cout << "Copy assignment" << std::endl; return *this; }
    CTest& operator=(CTest &&test) noexcept {
        Value = test.Value; Name = test.Name; std::cout << "Move assignment" << std::endl; return *this; }
    ~CTest() { std::cout << "Destructor" << std::endl; }
     
private:
    int Value;
    std::string Name;
};

int main()
{
    CTest t1(1, "hello");
    CTest t2(2, "hello");
    
    std::map<int, CTest> testMap;
    testMap[1] = t1;         //1
    testMap.emplace(2, t2);  //2
    testMap.emplace(3, CTest(3, "hello"));  //3
    testMap.emplace(std::piecewise_construct, std::forward_as_tuple(4), std::forward_as_tuple(4, "hello"));    //4
    testMap.emplace(std::piecewise_construct, std::forward_as_tuple(4), std::forward_as_tuple(std::move(t1))); //5
    return 0;
}

The output for each one is:

1
Default constructor
Copy assignment

2
Copy Constructor

3
Parameterized constructor
Move constructor
Destructor

4
Parameterized constructor

5
Move constructor
Destructor

1 involves the most copying: create an entry in the map with the default constructor, followed by copy assignment. I was surprised to see a destructor call for 3 and 5. In both cases the value passed is an rvalue. So is a temporary created from the rvalue passed in, which is deleted after use? This begs question what's the right way to use emplace? Should you just pass the arguments of the constructor, like in 4? That's best in performance as my results show.


Solution

  • Should you just pass the arguments of the constructor

    Yes, because this is literally what all emplace() functions are designed for. With insert(), you have to construct an object, and then [usually] copy it into your container. And generally, if you're using a container, you're only constructing so you can put them into the container. As you can see in your tests, it's a bit of extra work.

    emplace() was designed to allow you to construct directly into the container. And you do so by providing constructor parameters to the emplace function. insert() is used if you've already got an object and want to put it in a container.

    I had a snarky comment that others have noted is worth explaining a bit more. If your class (which I'll call Foo) has single parameter constructors, it may appear that you can do the same thing as emplace() by just passing the single parameter to something like insert() or push_back() or any place that would take a Foo as a parameter. This is a 'feature' of the language where the compiler will implicitly construct a Foo for you and use it. The problem is that under the hood, it's not doing the same thing. Where emplace() will build your object directly in the container, faking it by taking advantage of a single parameter constructor still causes copies to be made. Another downside to consider is this implicit conversion. It can hurt readability of your code or worse, break things. This can be avoided by marking the constructor as explicit.