marsl
marsl

Reputation: 1029

std::emplace_back invoking constructor of other objects in vector?

I am really confused about std::vector::emplace_back. I run the following code:

struct Thing {
    explicit Thing(std::string name) : name_{std::move(name)} {
        std::cout << "Constructed a thing called " << name_ << std::endl;
    }

    ~Thing() {
        std::cout << "Deconstructed a thing called " << name_ << std::endl;
    };
    std::string name_;
};


int main() {
    std::vector<Thing> things{Thing("A")};
    std::cout << "Size: " << things.size() << std::endl;
    things.emplace_back("B");
    std::cout << "Size: " << things.size() << std::endl;
}

and get this output:

Constructed a thing called A
Deconstructed a thing called A
Size: 1
Constructed a thing called B
Deconstructed a thing called A
Size: 2
Deconstructed a thing called A
Deconstructed a thing called B

Why on earth does things.emplace_back("B") invoke the deconstructor of the thing called A?

Upvotes: 1

Views: 106

Answers (2)

Artyer
Artyer

Reputation: 40791

What's happening is that when you add B to the vector, the vector probably only had capacity for 1 element, so had to move it's existing element into another vector (destructing the one in the original storage)

Let's add some more logging to see what happens:

#include <iostream>
#include <string>
#include <vector>

struct Thing {
    explicit Thing(std::string name) : name_{std::move(name)} {
        std::cout << "Constructed a thing called " << name_ << " at " << this << std::endl;
    }
    Thing(Thing&& t) noexcept : name_{std::move(t.name_)} {
        std::cout << "Moved a thing called " << name_ << " from " << &t << " to " << this << std::endl;
    }
    Thing(const Thing& t) : name_{t.name_} {
        std::cout << "Copied a thing called " << name_ << " from " << &t << " to " << this << std::endl;
    }

    ~Thing() {
        std::cout << "Deconstructed a thing called " << name_ << " at " << this << std::endl;
    };
    std::string name_;
};


int main() {
    std::vector<Thing> things{Thing("A")};
    std::cout << "Size: " << things.size() << " Capacity: " << things.capacity() << std::endl;
    things.emplace_back("B");
    std::cout << "Size: " << things.size() << " Capacity: " << things.capacity() << std::endl;
}

Example output:

Constructed a thing called A at 0x1111
Copied a thing called A from 0x1111 to 0x2222
Deconstructed a thing called A at 0x1111
Size: 1 Capacity: 1
Constructed a thing called B at 0x3333
Moved a thing called A from 0x2222 to 0x4444
Deconstructed a thing called A at 0x2222
Size: 2 Capacity: 2
Deconstructed a thing called A at 0x4444
Deconstructed a thing called B at 0x3333

Upvotes: 1

walnut
walnut

Reputation: 22152

std::vector stores the objects continuously in an allocated memory block. When that block's size becomes too small to add new elements into it, it must allocate a new, larger, memory block and copy/move the currently present elements in the vector into that new allocation. After that, the objects in the previous allocation are destroyed, involving the destructor call.

You are not seeing the copies being made, because you didn't define a custom copy constructor. If you did, you would see that a copy of A is copy constructed before the original A is destructed. If you define a noexcept move constructor, you will see that a copy of A is move constructed before the original A is destructed.

Upvotes: 3

Related Questions