Since C++ 11, we can use the keyword delete to declare functions as deleted and thus prohibit the use of these functions in the code. See this article for a brief overview.
In particular, the destructor of a class/struct/union can be declared as deleted. In today’s article, I am going to discuss this specific feature in detail with several examples.
It is useful to recall the basic principle of object creation in C++: When an object of a user-defined type is created, an appropriate constructor is automatically called, and when the object goes out of life, its destructor is automatically called (see this for the complete destructor semantics). Automatically, here, means that the compiler generates code behind the scenes to make sure these events happen without explicit intervention by the programmer.
The following code fragment illustrates this idea:
If the programmer does not define a destructor explicitly, then the destructor is implicitly defined by the compiler. This can be suppressed by using = delete in the destructor declaration:
What effect does this have on the code? Look at the following example.
Statements numbered (1), (3) and (5) are OK only if a valid destructor is present, and since the destructor is deleted in this case, the compiler generates error messages as expected.
What the above example also shows is that we can only create objects of class A on the heap, but then we cannot delete such an object. That is, of course, not a good practice because it will lead to memory leaks. What can we do in such situations?
What if we use the smart pointer unique_ptr to manage our object?
Unfortunately, this does not solve our problem, because when the unique_ptr object goes out of scope, it (by default) applies delete on the managed object, leading to a call of the destructor, and thus triggering the compiler error!
Fortunately, there is a way to address this problem. unique_ptr allows us to supply a custom deleter, which will be called when the object goes out of scope. Therefore, we define a (static) method in our class and pass a pointer to this function to the unique_ptr instance, along with our pointer.
This time we do not get any error because our destructor is not called. But it is very important to note that the responsibility for releasing the memory associated with our A object (obtained through new operator) rests with A::Deleter() function. Failure to handle this correctly will lead to runtime problems.
Effect on Inheritance
So far, we have considered different cases where the class with deleted destructor is used directly. What would happen if another class, say B, is derived from A?
When a class is designed to act as a base class in a hierarchy, its destructor is usually virtual (see this article). In accordance with this guideline, let us declare A::~A() as virtual and deleted. See what happens in this case.
The program does not compile. My Visual Studio 2019 (ver 16.4.5) gives the following errors:
Could it be because A::~A() is virtual? Let us see what happens if we remove the virtual keyword:
Unfortunately, even this does not go through. Here are the errors:
From the above two examples (and variants of these examples), we can conclude that it is impossible to meaningfully derive any class from a class with deleted destructor.
Let us consider the other possibility where a class with deleted destructor is derived from a normal base class.
We get an error when we compile this code (notice we have not instantiated Y at all):
Since the base destructor is virtual, destructors of all derived classes are virtual, and according to the language, it is not OK to delete an overriding virtual destructor. Hence the error. What if the base class destructor is not virtual?
The above is similar to Example-2, except for lines 18 and 19. While it is fine for a base class pointer to point to a derived class, it is not correct to delete the derived object through the base pointer when the base destructor is not virtual. This can cause undefined behaviour.
Effect on Composition
The next case we will consider is one where an object of a class with deleted destructor, is a member of another class. Look at the following code.
In the above example, the struct Compose contains a data member of type A. As expected, we get errors when we compile this code:
So, what do you think of deleted destructor? Should we use it? My general view is that if the situation warrants it, is up to the programmer to use any feature the language supports. After all, if deleting a destructor is never correct, then the language could have forbidden it. Having said that, I wish to add that a deleted destructor is probably quite rare in real world projects. I personally consider a private (or protected) destructor as a meaningful alternative to deleted destructor.
Hope you found today’s discussion informative. You can download the example programs from here.
Have a great day!
Recent Comments