Virtual Destructor and Undefined Behavior

Virtual destructor and undefined behavior

when/why should I use a virtual destructor?
Follow Herb Sutters guideline:

A base class destructor should be either public and virtual, or protected and nonvirtual

Can this be classified as an undefined behavior (we are aware that ~D() is not going to be called for sure) ?

It is Undefined Behavior as per the standard, which usually results in the Derived class destructor not being called and resulting in a memory leak, but it is irrelevant to speculate on after effetcs of an Undefined Behavior because standard doesn't gaurantee anything in this regard.

C++03 Standard: 5.3.5 Delete

5.3.5/1:

The delete-expression operator destroys a most derived object (1.8) or array created by a new-expression.

delete-expression:

::opt delete cast-expression

::opt delete [ ] cast-expression

5.3.5/3:

In the first alternative (delete object), if the static type of the operand is different from its dynamic type, the static type shall be a base class of the operand’s dynamic type and the static type shall have a virtual destructor or the behavior is undefined. In the second alternative (delete array) if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined.73)

What if ~D() is empty. Will it affect the code in any way ?
Still it is Undefined Behavior as per the standard, The derived class destructor being empty may just make your program work normally but that is again implementation defined aspect of an particular implementation, technically, it is still an Undefined Behavior.

Note that there is no gaurantee here that not making the derived class destructor virtual just does not result in call to derived class destructor and this assumption is incorrect. As per the Standard all bets are off once you are crossed over in Undefined Behavior land.

Note what he standard says about Undefined Behavior.

The C++03 Standard: 1.3.12 undefined behavior [defns.undefined]

behavior, such as might arise upon use of an erroneous program construct or erroneous data, for which this International Standard imposes no requirements. Undefined behavior may also be expected when this International Standard omits the description of any explicit definition of behavior. [Note: permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during
translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message). Many erroneous program constructs do not engender undefined behavior; they are required to be diagnosed.
]

If only derived destructor will be not called is governed by the bold text in the above quote, which is clearly left open for each implementation.

When to use virtual destructors?

Virtual destructors are useful when you might potentially delete an instance of a derived class through a pointer to base class:

class Base 
{
// some virtual methods
};

class Derived : public Base
{
~Derived()
{
// Do some important cleanup
}
};

Here, you'll notice that I didn't declare Base's destructor to be virtual. Now, let's have a look at the following snippet:

Base *b = new Derived();
// use b
delete b; // Here's the problem!

Since Base's destructor is not virtual and b is a Base* pointing to a Derived object, delete b has undefined behaviour:

[In delete b], if the static type of the
object to be deleted is different from its dynamic type, the static
type shall be a base class of the dynamic type of the object to be
deleted and the static type shall have a virtual destructor or the
behavior is undefined
.

In most implementations, the call to the destructor will be resolved like any non-virtual code, meaning that the destructor of the base class will be called but not the one of the derived class, resulting in a resources leak.

To sum up, always make base classes' destructors virtual when they're meant to be manipulated polymorphically.

If you want to prevent the deletion of an instance through a base class pointer, you can make the base class destructor protected and nonvirtual; by doing so, the compiler won't let you call delete on a base class pointer.

You can learn more about virtuality and virtual base class destructor in this article from Herb Sutter.

Undefined behaviour with non-virtual destructors - is it a real-world issue?

This is a never-ending question in the C++ tag: "What is the predictable undefined behavior". Easy to resolve all by yourself: get every C++ compiler implementation and check if the predictable unpredictable still works. This is however something you have to do by yourself.

Do post back what you found out, that would be quite useful to know. As long as the unpredictable has consistent and annotated behavior across the board. It makes it really hard for somebody that writes a C++ compiler to get anybody to pay attention to his product. Standardization by convention, happens a lot with a language that has a lot of undefined behavior.

What does 'has virtual method ... but non-virtual destructor' warning mean during C++ compilation?

If a class has a virtual method, that means you want other classes to inherit from it. These classes could be destroyed through a base-class-reference or pointer, but this would only work if the base-class has a virtual destructor. If you have a class that is supposed to be usable polymorphically, it should also be deletable polymorphically.

This question is also answered in depth here. The following is a complete example program that demonstrates the effect:

#include <iostream>

class FooBase {
public:
~FooBase() { std::cout << "Destructor of FooBase" << std::endl; }
};

class Foo : public FooBase {
public:
~Foo() { std::cout << "Destructor of Foo" << std::endl; }
};

class BarBase {
public:
virtual ~BarBase() { std::cout << "Destructor of BarBase" << std::endl; }
};

class Bar : public BarBase {
public:
~Bar() { std::cout << "Destructor of Bar" << std::endl; }
};

int main() {
FooBase * foo = new Foo;
delete foo; // deletes only FooBase-part of Foo-object;

BarBase * bar = new Bar;
delete bar; // deletes complete object
}

Output:

Destructor of FooBase
Destructor of Bar
Destructor of BarBase

Note that delete bar; causes both destructors, ~Bar and ~BarBase, to be called, while delete foo; only calls ~FooBase. The latter is even undefined behavior, so that effect is not guaranteed.

Missing Virtual Destructor Memory Effects

It certainly can do. Consider:

class A
{
public:
virtual void func() {}
};

class B : public A
{
public:
void func() { s = "Some Long String xxxxxx"; }
private:
std::string s;
// destructor of B will call `std::string` destructor.
};

A* func(bool b)
{
if (b)
return new B;
return new A;
}

...
A* a = func(true);
...
delete a;

Now, this will create a memory leak, as std::string s in the B object is not freed by A::~A - you need to call B::~B, which will only happen if the destructor is virtual.

Note that this applies to ALL compilers and all runtime systems that I'm aware of (which is all the common ones and some not so common ones).

Edit:

Based on the updated actual question: Memory de-allocation happens based on the allocated size, so if you can GUARANTEE that there NEVER is a single allocation happening because of the construction/use of the class, then it's safe to not have a virtual destructor. However, this leads to interesting issues if a "customer" of the base-class can make his/her own extension classes. Marking derived classes as final will protect against them being further derived, but if the base class is visible in a header-file that others can include, then you run the risk of someone deriving their own class from Base that does something that allocates.

So, in other words, in something like a PImpl, where the Impl class is hidden inside a source file that nobody else derives from, it's plausible to have this. For most other cases, probably a bad idea.

Is it safe to make destructor not virtual, and delete base pointer in special circumstances?

When the destructor is non virtual but there are only primitives types in derived, is it safe to call delete on base class ? (will there be no memory leaks ?)

You might not be aware of it, but these are two different questions.

The latter answer is: no, there won't be any memory leaks for this specific example, but there could be for other examples.

And the reason why is the answer to the former question: no, it is not safe to do this. This constitutes undefined behavior, even if the behavior is well-understood for nearly all compilers—and 'understood' is not synecticy for "is safe to do", just to be clear.

When you write code like delete mynode;, the compiler has to figure out which destructor to call. If the destructor for mynode is not virtual, then it will always use the base destructor, doing whatever the base destructor needs to do, but not whatever the derived destructor needs to do.

In this case, that's not such a big deal: the only thing AVL_Node adds is a locally allocated int variable, which will be cleaned up as part of the same process that cleans up the whole pointer.

But if your code were like this instead:

struct AVL_Node : public BST_Node {
std::unique_ptr<int> height = std::make_unique<int>();
};

Then this code would definitely cause memory leaks, even though we've expressly used a smart pointer in the construction of the derived object! The smart pointer doesn't save us from the tribulations of deleteing a base pointer with a non-virtual destructor.

And in general, your code could cause any kind of leak, including but not limited to resource leaks, file handle leaks, and so on, if AVL_Node were responsible for other objects. Consider, for example, if AVL_Node had something like this, which is extremely common in certain kinds of graphics code:

struct AVL_Node : public BST_Node {
int handle;
AVL_Node() {
glGenArrays(1, &handle);
}
/*
* Pretend we implemented the copy/move constructors/assignment operators as needed
*/
~AVLNode() {
glDeleteArrays(1, &handle);
}
};

Your code wouldn't leak memory (in your own code), but it would leak an OpenGL object (and any memory allocated by that object).

What is the rule when declaring a destructor virtual in derived class only ?

If you never plan to store a pointer to the base class, then this is fine.

It's also unnecessary unless you plan to also create further derived instances of the derived class.

So here's the example we'll use for the sake of clarity:

struct A {
std::unique_ptr<int> int_ptr = std::make_unique<int>();
};

struct B : A {
std::unique_ptr<int> int_ptr_2 = std::make_unique<int>();
virtual ~B() = default;
};

struct C : B {
std::unique_ptr<int> int_ptr_3 = std::make_unique<int>();
//virtual ~C() = default; // Unnecessary; implied by B having a virtual destructor
};

Now here's all the the code that's safe and unsafe to use with these three classes:

auto a1 = std::make_unique<A>(); //Safe; a1 knows its own type
std::unique_ptr<A> a2 = std::make_unique<A>(); //Safe; exactly the same as a1
auto b1 = std::make_unique<B>(); //Safe; b1 knows its own type
std::unique_ptr<B> b2 = std::make_unique<B>(); //Safe; exactly the same as b1
std::unique_ptr<A> b3 = std::make_unique<B>(); //UNSAFE: A does not have a virtual destructor!
auto c1 = std::make_unique<C>(); //Safe; c1 knows its own type
std::unique_ptr<C> c2 = std::make_unique<C>(); //Safe; exactly the same as c1
std::unique_ptr<B> c3 = std::make_unique<C>(); //Safe; B has a virtual destructor
std::unique_ptr<A> c4 = std::make_unique<C>(); //UNSAFE: A does not have a virtual destructor!

So if B (a class with a virtual destructor) inherits from A (a class without a virtual destructor), but as a programmer, you promise you will never refer to an instance of B with an A pointer, then you have nothing to worry about. So in that case, like my example tries to show, there may be valid reasons to declare the destructor of a derived class virtual while leaving the super class' destructor non-virtual.

Implicitly declared destructor

Is the implicitly declared destructor B::~B() virtual?

No, as per class.dtor/3:

An implicitly-declared prospective destructor for a class X will have
the form

~X()

and, naturally, as per class.dtor/12:

If a class has a base class with a virtual destructor, its destructor (whether user- or implicitly-declared) is virtual



If not, should I always declare a virtual destructor when using inheritance?

The C++ Core Guidelines, C.35 advices to make base class destructors that are public to be virtual:

C.35: A base class destructor should be either public and virtual, or protected and non-virtual

Reason To prevent undefined behavior. If the destructor is public, then calling code can attempt to destroy a derived class object
through a base class pointer, and the result is undefined if the base
class’s destructor is non-virtual. If the destructor is protected,
then calling code cannot destroy through a base class pointer and the
destructor does not need to be virtual; it does need to be protected,
not private, so that derived destructors can invoke it. In general,
the writer of a base class does not know the appropriate action to be
done upon destruction.

[...]

Note A virtual function defines an interface to derived classes that can be used without looking at the derived classes. If the
interface allows destroying, it should be safe to do so.

[...]

Exception We can imagine one case where you could want a protected virtual destructor: When an object of a derived type (and only of such
a type) should be allowed to destroy another object (not itself)
through a pointer to base. We haven’t seen such a case in practice,
though.

Enforcement

  • A class with any virtual functions should have a destructor that is either public and virtual or else protected and non-virtual.

What happens when delete a polymorphic object without a virtual destructor?

What happens when b is deleted without a virtual destructor?

We don't know. The behavior is undefined. For most actual cases the destructor of Derived might no be invoked, but nothing is guaranteed.

5.3.5 Delete
[expr.delete]

(emphasis mine)

In the first alternative (delete object), if the static type of the
object to be deleted is different from its dynamic type, the static
type shall be a base class of the dynamic type of the object to be
deleted and the static type shall have a virtual destructor or the
behavior is undefined
.



Related Topics



Leave a reply



Submit