Reputation: 678
I have read this article here: When to use virtual destructors?
and I got the idea that, whenever we create an object dynamically using new
or smart pointers, the base class should have a proper virtual
destructor for destruction of objects at deletion.
Then I have found some code like follows(Simplified form), which has missed the virtual
destructor in the Base
:
class Base
{
public:
// some static members
};
class Derived1 final : public Base
{
public:
// other members
// default constructor does not construct the `Base` in constructor member intilizer
Derived1() {};
virtual ~Derived1() = default;
};
class Derived2 final : public Base
{
public:
// other members
Derived2() {}; // default constructor does not construct the `Base`
~Derived2() = default;
};
int main()
{
// creating Derived1 dynamically // 1
Derived1 *d1Object = new Derived1{};
// creating Derived2 dynamically // 2
Derived2 *d2Object1 = new Derived2{};
// creating Derived2 statically // 3
Derived2 d2Object2{};
// clean up
delete d1Object;
delete d2Object1;
}
My qestion is:
1, 2, 3
) ? Why?Base
, in the member initializer lists of the constructors of the both derived classes(in the above particular case)?I am using C++11.
Upvotes: 1
Views: 114
Reputation: 545943
The object pointers in that code aren’t actually polymorphic: the static type of *d1Object
is the same as its dynamic type, i.e. Derived1&
(the same is true for the other objects).
As a consequence, destroying it directly calls the correct destructor, ~Derived1
. Thus everything is fine. An issue occurs in the following code:
Base* b = new Derived1();
delete b;
This implicitly calls b->~Base();
. And since ~Base
isn’t virtual, ~Derived1
does not get called, and you consequently get UB.
The same is true for std::unique_ptr<Base>
. However, it’s not true for std::shared_ptr<Base>
because the shared_ptr<>
constructor is templated, and stores the destructor of the actual object it’s constructed with. That is, the following is fine and invokes the correct destructors for both objects:
std::shared_ptr<Base> p1{new Derived1{});
std::shared_ptr<Base> p2 = std::make_shared<Derived1>();
As for your question regarding constructors, the same as for data members holds: even if they are missing from the initialiser list they are still getting default initialised in the order of their declaration and, in the case of base classes, in the order from most distant to most recent parent. In other words, you can’t not initialise a parent class, it always happens.
Upvotes: 6
Reputation: 1907
Question 1: Do I have Undefined Behavior in any of the cases(1, 2, 3) ? Why?
There is no undefined behavior in the provided code sample.
There would be undefined behavior, if you were to try to hold a pointer to Base
and delete through that pointer. In the provided examples, you know the concrete class that is being deleted
Question 2: Isn't essential to construct the Base, in the member initializer lists of the constructors of the both derived classes(in the above particular case)?
The base class constructor is always called, no matter if you explicitly call it. If there is no explicit invocation, the default constructor will be invoked. If there is no default, no-argument constructor and you do not invoke a specific constructor, the code would fail to compile.
Upvotes: 3