简体   繁体   中英

How to use `std::vector.back()` correctly?

I have a bug in my code which I don't quite understand. According to the documentation, std::vector.back() returns a reference to the last element in the container, so here's what I did: ( live here )

#include <iostream>
#include <vector>

class Foo {
  public:
    Foo(int id) : id(id) {
        std::cout << "foo " << id << " constructed" << std::endl;
    }
    ~Foo() {
        std::cout << "foo " << id << " destructed" << std::endl;
    }

    int id;
};

int main() {
    std::vector<Foo> foos;
    
    for (int i = 0; i < 2; i++) {
        foos.emplace_back(i);  // construct foo in place
        
        auto& foo = foos.back();
        std::cout << "Play with " << foo.id << std::endl;
    }
    
    for (auto&& foo : foos) {
        std::cout << "I'm foo " << foo.id << std::endl;
    }
    
    return 0;
}

which yields the following output:

foo 0 constructed
Play with 0
foo 1 constructed
foo 0 destructed
Play with 1
I'm foo 0
I'm foo 1
foo 0 destructed
foo 1 destructed

So, right after the second instance of Foo is constructed, the first one gets destructed, why? because auto& foo goes out of scope? But when I print the foos vector, it still has two foos, what a surprise! At first glance it seems like foos.back() is making a copy, which is not true. What I really don't understand is, how is it possible that the constructor is called twice but the destructor is called 3 times, shouldn't they always come in pair?

What's happening here is that when you .emplace_back(1) the std::vector does not have enough space for two elements, so it has to reallocate, copy/move all the existing elements to the new allocation, then emplace_back the new one. Hence you see the only existing element being destroyed from the old allocation.

This goes away if you .reserve(2) before the loop: https://godbolt.org/z/oj8fWhznP

foo 0 constructed
Play with 0
foo 1 constructed
Play with 1
I'm foo 0
I'm foo 1
foo 0 destructed
foo 1 destructed

You can also use .capacity() to check the size of the allocation, which indeed confirms the theory: https://godbolt.org/z/a88xfqa7f

--- 0 ---
capacity: 0
foo 0 constructed
capacity: 1
Play with 0
--- 1 ---
capacity: 1
foo 1 constructed
foo 0 destructed
capacity: 2
Play with 1

Adding logging definitions for move/copy constructors and assignment operators might give you better insight into what std::vector is doing internally.

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