简体   繁体   中英

Why emplace_back does matter?

#include <iostream>
#include <vector>

struct T{
    T(){
        std::cout << "Constructor\n";
    }
    ~T(){
        std::cout << "Destructor\n";
    }   
};

int main() {
    std::vector<T> vec;
    vec.push_back(T());
    vec.push_back(T());

    return 0;
}

The output is:

(1)Constructor
(2)Destructor
(3)Constructor
(4)Destructor
(5)Destructor
(6)Destructor
(7)Destructor

Why there is so much desructors calls? I see that:

(1) consruct temporary object temp1

(2) destruct temp1

(3) consruct temporary object temp2

(4) destruct temp2

Then it was called copy constructor or move constructor for temp1 and temp 2. So, (5) and (6) are clear. But what about (7)?

Let's expand your structure a bit:

struct T {
    T() {
        std::cout << "Constructor\n";
    }

    T(const T&) {
        std::cout << "Copy Constructor\n";
    }

    T(T&&) {
        std::cout << "Move Constructor\n";
    }

    ~T() {
        std::cout << "Destructor\n";
    }
};

And separate calls to push_back method:

vec.push_back(T()); // 1
std::cout << "--- --- ---\n";

vec.push_back(T()); // 2
std::cout << "--- --- ---\n";

Now the output looks more complete:

Constructor
Move Constructor
Destructor
--- --- ---
Constructor
Move Constructor
Copy Constructor
Destructor
Destructor
--- --- ---
Destructor
Destructor

The first group:

Constructor
Move Constructor
Destructor

corresponds to the first push_back call:

vec.push_back(T()); // 1

The output might be decrypted easily:

Constructor // Create a temporary
Move Constructor // Move a temporary into the internal vector storage
Destructor // Destroy a temporary

The second group:

Constructor
Move Constructor
Copy Constructor
Destructor
Destructor

corresponds to the second push_back call:

vec.push_back(T()); // 2

and a little bit more complicated:

Constructor // create a temporary
Move Constructor // move it into the newly allocated vector storage
Copy Constructor // copy previously created element into the new storage
Destructor // destroy old storage
Destructor // destroy temporary

Here you should remember that vector class allocates its memory internally and then manages it to provide enogh space for all elements. So, if you add more elements, new allocations happen and old elements are copied or moved into the new storage.

In case of known size you might use reserve method, which simply reserves enough memory for a particular number of elements. It allows to avoid unnecessary memory reallocations and copying or moving elements during these reallocations on adding new elements into the vector (at least until you don't exceed the reserved size).

The third group:

Destructor
Destructor

corresponds to the vector vec destructor call at the end of the program.

emplace_back matters for "move only" types (like std::unique_ptr).

This is wrong and an oversimplification. Not all containers are created equally. For your vector example, if we use reserve the implementation can do a move assign rather than a construct, eliding our copy/extraneous destructor:

std::vector<T> v;
v.reserve(2);
v.emplace_back(1);
v.emplace_back(1);

Output:

Constructor
Constructor
---
Destructor
Destructor

For a set:

std::set<T> s;
s.emplace(1);
s.emplace(1);

We get the same output. Why though? A set should theoretically construct only one object since sets are unique right? In actuality, the typical implementation constructs a temporary node in order to perform the comparison, accounting for the extra construct/destruct even though it never enters the container.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM