3
  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
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Dom
  • 31
  • 2
  • 5
    You should get a [good C++ book](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) and learn from that. It will surely mention copy constructors and the Rule of Three. – Yksisarvinen Jun 27 '19 at 23:33
  • 5
    Note: since you defined `~Base()`, this suppresses implicit generation of the move-constructor (and may also suppress copy-constructor in future versions of the language). You probably want to define the move-constructor, and either default it, or make it `noexcept`. – M.M Jun 27 '19 at 23:49
  • 1
    You still didn't accept any answer. That's not nice. [SO: What should I do when someone answers my question?](https://stackoverflow.com/help/someone-answers) – Scheff's Cat Jul 20 '19 at 09:11

3 Answers3

10

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();
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
9

You're missing the point of emplace. Emplace functions construct the object in-place from the given arguments, as opposed to e.g. 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 (i.e. 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 (i.e. the move constructor), copying it, and then the original i.e. 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.

Cruz Jean
  • 2,761
  • 12
  • 16
8

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

milleniumbug
  • 15,379
  • 3
  • 47
  • 71
  • That noisy template is a great idea. Had I used it initially, I would have answered my own question. Thanks a lot – Dom Jun 28 '19 at 02:48
  • @Dom If this answered your question, please consider accepting the answer (there is a grayed-out checkbox to the left). – Ted Lyngmo May 08 '21 at 20:47