简体   繁体   中英

Why is emplace_back() behaving like this?

  1. Why does ~Base() get called immediately after the call to emplace_back()
  2. Why is sayHello() accessible after the destructor call
  3. Why does ~Base() get called again
#include <iostream>
#include <vector>

class Base
{
    private:

        static int m_count;

    public:

        Base()
        {
            std::cout << " Base created. Count = " << ++m_count << std::endl;
        }

        ~Base()
        {
            std::cout << " Base destroyed. Count = " << --m_count << std::endl;
        }

        void sayHello() const
        {
            std::cout << " Base says hello" << std::endl;
        }
};

int Base::m_count = 0;

int main()
{
    {
        std::vector< Base > vBase;

        vBase.emplace_back ( Base() );  // <- Why does ~Base() get called here

        vBase[0].sayHello(); // <- Why is this function accessible after call to dtor
    }
    return 0;
}

Program output...

Base created. Count = 1  
Base destroyed. Count = 0  
Base says hello
Base destroyed. Count = -1

In the call vBase.emplace_back ( Base() ); you first create a Base object. The vector creates another Base in place and the resources owned by the first Base are then moved into the new one. The first base is then deleted. Inside your vector you now have a moved constructed Base which is why the call to sayHello() works.

What you probably want to do is to let emplace_back actually construct the object, in place without creating a temporary object manually. You do that by only supplying the arguments needed to construct a Base . Like so:

vBase.emplace_back();

You're missing the point of emplace. Emplace functions construct the object in-place from the given arguments, as opposed to eg push_back which copy-constructs it from a pre-existing object. You should have written vBase.emplace_back() which constructs the object inside the vector in-place with no constructor arguments (ie default constructed).

As it stands you're effectively default constructing a Base object via Base() , passing it to emplace, which calls the constructor taking a Base object (ie the move constructor), copying it, and then the original ie the Base() object is destroyed.

Its copy is still in the vector, which is why you can still access it. What was destroyed was the temporary. The second destructor call is the copy being destroyed when the vector goes out of scope.

So you're basically just doing the same thing as push_back .

You're not including the objects created by move and copy constructors in your counter, nor are you logging the calls. If you make changes to your logging to fix the Rule of Three/Five violation, you'll see this:

#include <typeinfo>
#include <iostream>

/// noisy
///
/// A class logs all of the calls to Big Five and the default constructor
/// The name of the template parameter, received as if by call
/// to `typeid(T).name()`, is displayed in the logs.
template<typename T>
struct noisy
{
    noisy& operator=(noisy&& other) noexcept { std::cout << "MOVE ASSIGNMENT<" << typeid(T).name() << ">(this = " << this << ", other = " << &other << ")\n"; return *this; }
    noisy& operator=(const noisy& other) { std::cout << "COPY ASSIGNMENT<" << typeid(T).name() << ">(this = " << this << ", other = " << &other << ")\n"; return *this; }
    noisy(const noisy& other) { std::cout << "COPY CONSTRUCTOR<" << typeid(T).name() << ">(this = " << this << ", other = " << &other << ")\n"; }
    noisy(noisy&& other) noexcept { std::cout << "MOVE CONSTRUCTOR<" << typeid(T).name() << ">(this = " << this << ", other = " << &other << ")\n"; }
    ~noisy() { std::cout << "DESTRUCTOR<" << typeid(T).name() << ">(this = " << this << ")\n"; }
    noisy() { std::cout << "CONSTRUCTOR<" << typeid(T).name() << ">(this = " << this << ")\n"; }
};

#include <iostream>
#include <vector>

class Base : public noisy<Base>
{
    public:

        void sayHello() const
        {
            std::cout << "Base says hello" << "(this = " << this << ")" << std::endl;
        }
};

int main()
{
    {
        std::vector< Base > vBase;

        vBase.emplace_back ( Base() );  // <- Why does ~Base() get called here

        vBase[0].sayHello(); // <- Why is this function accessible after call to dtor
    }
    return 0;
}

Output:

CONSTRUCTOR<4Base>(this = 0x7fff300b580f)
MOVE CONSTRUCTOR<4Base>(this = 0x18a6c30, other = 0x7fff300b580f)
DESTRUCTOR<4Base>(this = 0x7fff300b580f)
Base says hello(this = 0x18a6c30)
DESTRUCTOR<4Base>(this = 0x18a6c30)

Live on Coliru

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