Does a default virtual destructor prevent compiler-generated move operations? - c++

Inspired by the post Why does destructor disable generation of implicit move methods?, I was wondering if the same is true for the default virtual destructor, e.g.
class WidgetBase // Base class of all widgets
{
public:
virtual ~WidgetBase() = default;
// ...
};
As the class is intended to be a base class of a widget hierarchy I have to define its destructor virtual to avoid memory leaks and undefined behavior when working with base class pointers. On the other hand I don't want to prevent the compiler from automatically generating move operations.
Does a default virtual destructor prevent compiler-generated move operations?

Yes, declaring any destructor will prevent the implicit-declaration of the move constructor.
N3337 [class.copy]/9: If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared
as defaulted if and only if
X does not have a user-declared copy constructor,
X does not have a user-declared copy assignment operator,
X does not have a user-declared move assignment operator,
X does not have a user-declared destructor, and
the move constructor would not be implicitly defined as deleted.
Declaring the destructor and defining it as default counts as user-declared.
You'll need to declare the move constructor and define it as default yourself:
WidgetBase(WidgetBase&&) = default;
Note that this will in turn define the copy constructor as delete, so you'll need to default that one too:
WidgetBase(const WidgetBase&) = default;
The rules for copy and move assignment operators are pretty similar as well, so you'll have to default them if you want them.

Not a solution, but one of possible workarounds.
You can inherit all of your classes from a class that has only default virtual destructor.
I checked using GCC 9 and Apple's Clang++ with -std=c++17: both of them generate move constructors for classes that inherit the class below.
class Object {
public:
virtual ~Object() = default;
};
The class below will indeed have a move constructor.
class Child : public Object {
public:
Child(std::string data) : data(data) {
}
private:
std::string data;
};
Another possible but risky workaround would be to not declare virtual destructors at all. It would introduce the following risks:
All objects must always be destructed by someone who knows their exact type. Which is not really that big of a problem in a nicely designed C++ code.
When object of such class is stored in a container like std::vector or std::list it must always be wrapped using std::shared_ptr. std::unique_ptr would cause leaks! That's related to their differences related to storing deleter.

Related

C++ Primer 5th Ed - Stanley : Can a base class with empty destructor making it virtual?

I am reading the below excerpts from the above book (chapter 15 - Object Oriented Programming Section 15.7.1 Virtual Destructors).
Destructors for base classes are an important exception to the rule of thumb that if a class needs a destructor, it also needs copy and assignment (§13.1.4, p. 504). A base class almost always needs a destructor, so that it can make the destructor virtual.
If a base class has an empty destructor in order to make it virtual, then the fact that the class has a destructor does not indicate that the assignment operator or copy constructor is also needed.
I don't understand the part where it mentioned "if a base class has an empty destructor, so that I can make it virtual". I thought the only way to make it virtual on a base class is to have the keyword "virtual".
Any explanation be great!
The author here considers an empty virtual destructor to be a destructor that is still user-declared (not one that is implicitly-declared as virtual because of a base class destructor) and either defined with an empty body or defaulted:
virtual ~MyClass() = default;
or
virtual ~MyClass() {}
It is impossible to force an implicitly-declared destructor to be virtual (if there isn't already a base class with a virtual destructor) and so either of these are the only possibilities to make the destructor virtual but have it behave the same way that the implicitly-defined (non-virtual) one would. That's why the author says "in order to make it virtual".
The quote is saying that having either of these in a class doesn't indicate that the class semantically needs to define its own copy assignment operator or copy constructor. The destructor still does exactly the same as if it was implicitly-defined and so the implicitly-defined copy operations are likely to still be semantically correct.
A non-empty destructor, e.g. one that performs some non-trivial work with the class (not just something like simple logging etc.) is usually an indication that the implicitly defined copy operations will not be semantically correct for the intended behavior of the class, which is why there is the mentioned rule-of-three saying that in such situations copy operations should be manually defined as well to avoid the unintended semantics.
However, in connection with move semantics there is still a problem with an empty virtual destructor. Declaring any destructor in any way will inhibit implicit declaration of the move constructor and move assignment operator.
As a consequence, if your class has e.g. a std::vector member, it will become inefficient, since it can never be moved, only copied. Usually a class with virtual destructor is used polymorphically and not copied/moved around, but that is not always the case. So it may make sense to still explicitly default all move and copy operations (in line with the rule-of-five) in such a situation:
virtual ~MyClass() = default;
MyClass(const MyClass&) = default;
MyClass(MyClass&&) = default;
MyClass& operator=(const MyClass&) = default;
MyClass& operator=(MyClass&&) = default;
Or (probably more likely), given that the class may be intended to only be used polymorphically, explicitly delete all of them to make it impossible for object slicing to occur unintentionally (see C++ core guidelines C.67):
virtual ~MyClass() = default;
MyClass(const MyClass&) = delete;
MyClass(MyClass&&) = delete;
MyClass& operator=(const MyClass&) = delete;
MyClass& operator=(MyClass&&) = delete;
First, it means that, for a base class Base, you'd likely need a virtual destructor:
class Base {
public:
virtual ~Base() {} // note the empty body
};
This is so that deleting a descendant via base pointer calls the proper ctor and frees memory correctly. Note that copy/assignment is not needed here; at this point, we don't even know if the descendant class is expected to have data members or simply fulfills an interface without any data.
This is, however, only true if you're using naked and some of the smart pointers (e.g. std::unique_ptr<>). When you use std::shared_ptr<>, your destructor is automatically saved so you don't need virtual destructors to keep a std::shared_ptr<Descendant> in a std::shared_ptr<Base> before destroying it.

Move constructor disappears in derived class when adding custom destructor

I have a move-only Base class and a Derived which inherits Base's constructors. I would like to give a Derived a custom destructor, but when I do so it no longer inherits Base's move constructor. Very mysterious. What is happening?
godbolt
// move-only
struct Base {
Base() = default;
Base(Base const &) = delete;
Base(Base &&) {}
};
struct Derived : public Base {
using Base::Base;
// remove this and it all works
~Derived() { /* ... */ }
};
int main() {
Base b;
// works
Base b2 = std::move(b);
Derived d;
// fails
Derived d2 = std::move(d);
}
The move constructor is not inherited with using Base::Base; in the way that you seem to think it is, because the move constructor in Base does not have the signature that a move constructor in Derived would have. The former takes a Base&&, the latter a Derived&&.
Then in Derived you are declaring a destructor. This inhibits the implicit declaration of a move constructor for Derived. So there is no move constructor in Derived.
The compiler then falls back to Derived's implicitly generated copy constructor for Derived d2 = std::move(d);. But that is defined as deleted because the base class of Derived is not copy-able. (You manually deleted Bases copy constructor.)
In overload resolution the deleted copy constructor is chosen over the Base classes inherited Base(Base&&) constructor (although a Derived rvalue could bind to Base&&), because the latter requires a conversion sequence that is not considered exact match, while binding to a const Derived& is considered exact match for the purpose of overload resolution.
Also there is the proposed wording for the resolution of CWG issue 2356 which would exclude the inherited Base move constructor from participating in overload resolution at all. (From what I can tell this is what the compiler are implementing already.)
If you don't have a good reason to declare a destructor, don't do so. If you do have a reason, then you need to default the move operations again, as you did for the move constructor in Base. (You probably want to default the move assignment operator as well if the classes are supposed to be assignable.)
If you intend to use the class hierarchy polymorphically, you should declare a virtual (defaulted) destructor in the polymorphic base, but you do not need to declare a destructor in the derived classes.
Move constructors are generated under specific circumstances.
https://en.wikipedia.org/wiki/Special_member_functions
In creating a destructor, you have stopped the generation of a move constructor by the compiler.
Also, create a virtual Base destructor if you don't have one. Default it if it doesn't have to do anything special. Same with your Base move constructor, just don't leave it empty, declare it default. You're using =delete, use =default as well.
The inherited move constructor does not have the signature for the derived class.
In the first case without the explicitly declared destructor the compiler implicitly declares the default move constructor for the derived class.
In the second case when the destructor is explicitly declared the move constructor is not implicitly declared by the compiler.
From the C++ 17 Standard (15.8.1 Copy/move constructors)
8 If the definition of a class X does not explicitly declare a move
constructor, a non-explicit one will be implicitly declared as
defaulted if and only if
(8.1) X does not have a user-declared copy constructor,
(8.2) X does not have a user-declared copy assignment operator,
—(8.3) X does not have a user-declared move assignment operator, and
> —(8.4) X does not have a user-declared destructor.
But in any case the ,move constructor of the base class is not the move constructor of the derived class due to different signatures.

Warning: definition of implicit copy constructor is deprecated

I have a warning in my C++11 code that I would like to fix correctly but I don't really know how. I have created my own exception class that is derived from std::runtime_error:
class MyError : public std::runtime_error
{
public:
MyError(const std::string& str, const std::string& message)
: std::runtime_error(message),
str_(str)
{ }
virtual ~MyError()
{ }
std::string getStr() const
{
return str_;
}
private:
std::string str_;
};
When I compile that code with clang-cl using /Wall I get the following warning:
warning: definition of implicit copy constructor for 'MyError' is deprecated
because it has a user-declared destructor [-Wdeprecated]
So because I have defined a destructor in MyError no copy constructor will be generated for MyError. I don't fully understand if this will cause any issues...
Now I could get rid of that warning by simply removing the virtual destructor but I always thought that derived classes should have virtual destructors if the base class (in this case std::runtime_error) has a virtual destructor.
Hence I guess it is better not to remove the virtual destructor but to define the copy constructor. But if I need to define the copy constructor maybe I should also define the copy assignment operator and the move constructor and the move assignment operator. But this seems overkill for my simple exception class!?
Any ideas how to best fix this issue?
You do not need to explicitly declare the destructor in a derived class:
§ 15.4 Destructors [class.dtor] (emphasis mine)
A destructor can be declared virtual (13.3) or pure virtual (13.4); if
any objects of that class or any derived class are created in the
program, the destructor shall be defined. If a class has a base class
with a virtual destructor, its destructor (whether user- or
implicitly-declared) is virtual.
In fact, it might even be detrimental to performance in some cases, as explicitly declaring a destructor will prevent the implicit generation of a move constructor and move assignment operator.
Unless you need to do something in your destructor, the best course of action would be to just omit an explicit declaration of a destructor.
If you do need a custom destructor, and are certain that the default copy ctor, copy assignment operator, move ctor and move assignment operator would do the correct thing for you, it is best to explicitly default them like so:
MyError(const MyError&) = default;
MyError(MyError&&) = default;
MyError& operator=(const MyError&) = default;
MyError& operator=(MyError&&) = default;
Some reasoning on why you're seeing the error, because this used to be perfeclty valid code in C++98:
As of C++11, implicit generation of the copy constructor is declared as deprecated.
§ D.2 Implicit declaration of copy functions [depr.impldec]
The implicit definition of a copy constructor as defaulted is
deprecated if the class has a user-declared copy assignment operator
or a user-declared destructor. The implicit definition of a copy
assignment operator as defaulted is deprecated if the class has a
user-declared copy constructor or a user-declared destructor (15.4,
15.8). In a future revision of this International Standard, these implicit definitions could become deleted (11.4).
The rationale behind this text is the well-known Rule of three.
All quotes below are sourced from cppreference.com: https://en.cppreference.com/w/cpp/language/rule_of_three
Rule of Three
If a class requires a user-defined destructor, a user-defined copy
constructor, or a user-defined copy assignment operator, it almost
certainly requires all three.
The reason why this rule of thumb exists is because the default generated dtor, copy ctor and assignment operator for handling different types of resources (most notably pointers to memory, but also others, like file descriptors and network sockets to name just a couple) rarely do the correct behaviour. If the programmer thought that he needed special handling for the closing of a file handle in the class destructor, he most surely wants to define how this class should be copied or moved.
For completeness, below are the often related Rule of 5, and the somewhat disputed Rule of Zero
Rule of Five
Because the presence of a user-defined destructor, copy-constructor,
or copy-assignment operator prevents implicit definition of the move
constructor and the move assignment operator, any class for which move
semantics are desirable, has to declare all five special member
functions:
Rule of Zero
Classes that have custom destructors, copy/move constructors or
copy/move assignment operators should deal exclusively with ownership
(which follows from the Single Responsibility Principle). Other
classes should not have custom destructors, copy/move constructors or
copy/move assignment operators.
Now I could get rid of that warning by simply removing the virtual destructor but I always thought that derived classes should have virtual destructors if the base class (in this case std::runtime_error) has a virtual destructor.
You thought wrong. Derived classes will always have virtual destructor if you define one in base, no matter if you create it explicitly or not. So removing destructor would be simplest solution. As you can see in documentation for std::runtime_exception it does not provide it's own destructor either and it is compiler generated because base class std::exception does have virtual dtor.
But in case you do need destructor you can explicitly add compiler generated copy ctor:
MyError( const MyError & ) = default;
or prohibit it making class not copyable:
MyError( const MyError & ) = delete;
the same for assignment operator.
Note: the same happens for much different code but I'm writing it here in case someone gets the same warning.
There was a bug in GCC versions 6.4 - 9.0 where using declarations for base_type's operator= and base_type's ctor in types inherited from base_type which was a class template did not actually create copy/move ctor/operators (ending in very unexpected compiler errors that an object can not be copied/moved).
Since GCC 9.0, the bug is fixed but it creates this warning instead. The warning is wrong and should not appear (the using explicitly declares constructors/operators).
Example code with workarounds and GCC version comparison: https://godbolt.org/z/WgIH4c
GCC bug report: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89381
another GCC bug report: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=92145
Origin of this discovery: https://github.com/boostorg/spirit/issues/465
This will make your code compile (but not work) in clang 13:
MyError(const MyError&) {};
MyError(MyError&&) {};
MyError& operator=(const MyError&) {};
MyError& operator=(MyError&&) {};
You will need to fill in the appropriate code for the copy constructor,
but also be aware that you don't need all 4 functions, only the ones that are being called.

Must a c++ interface obey the rule of five?

What is the correct way to declare instantiation methods when defining an interface class?
Abstract base classes are required to have a virtual destructor for obvious reasons. However, the following compilation warning is then given: "'InterfaceClass' defines a non-default destructor but does not define a copy constructor, a copy assignment operator, a move constructor or a move
assignment operator", which is the 'rule of five'.
I understand why the 'rule of five' should be obeyed in general, but is it still applicable for an abstract base class or interface?
My implimentation is then:
class InterfaceClass
{
// == INSTANTIATION ==
protected:
// -- Constructors --
InterfaceClass() = default;
InterfaceClass(const InterfaceClass&) = default;
InterfaceClass(InterfaceClass&&) = default;
public:
// -- Destructors --
virtual ~InterfaceClass() = 0;
// == OPERATORS ==
protected:
// -- Assignment --
InterfaceClass& operator=(const InterfaceClass&) = default;
InterfaceClass& operator=(InterfaceClass&&) = default;
// == METHODS ==
public:
// Some pure interface methods here...
};
// == INSTANTIATION ==
// -- Destructors --
InterfaceClass::~InterfaceClass()
{
}
Is this correct? Should these methods be = delete instead? Is there some way of declaring the destructor to be virtual pure whilst also somehow remaining default?
Even if I declare the destructor as: virtual ~InterfaceClass() = default;, if I do not explicitly default the other four then I will get the same compiler warning.
Tl;dr: What is the correct way to satisfy the 'rule of five' for an interface class as the user must define a virtual destructor.
Thanks for your time and help!
Is this correct? Should these methods be = delete instead?
Your code seems correct. The need of defining special copy/move member functions as default and protected comes clear when you try to copy a derived class polymorphycally. Consider this additional code:
#include <iostream>
class ImplementationClass : public InterfaceClass
{
private:
int data;
public:
ImplementationClass()
{
data=0;
};
ImplementationClass(int p_data)
{
data=p_data;
};
void print()
{
std::cout<<data<<std::endl;
};
};
int main()
{
ImplementationClass A{1};
ImplementationClass B{2};
InterfaceClass *A_p = &A;
InterfaceClass *B_p = &B;
// polymorphic copy
*B_p=*A_p;
B.print();
// regular copy
B=A;
B.print();
return 0;
}
And consider 4 options for defining special copy/move member functions in your InterfaceClass.
copy/move member functions = delete
With special copy/move member functions deleted in your InterfaceClass, you would prevent polymorphic copy:
*B_p = *A_p; // would not compile, copy is deleted in InterfaceClass
This is good, because polymorphic copy would not be able to copy the data member in the derived class.
On the other hand, you would also prevent normal copy, as the compiler won't be able to implicitly generate a copy assignment operator without the base class copy assignment operator:
B = A; // would not compile either, copy assignment is deleted in ImplementationClass
copy/move special member functions public
With copy/move special member functions as default and public, (or without defining copy/move member functions), normal copy would work:
B = A; //will compile and work correctly
but polymorphic copy would be enabled and lead to slicing:
*B_p = *A_p; // will compile but will not copy the extra data members in the derived class.
copy/move special member functions not defined
If move&copy special member functions are not defined, behavior with respect to copy is similar to 2: the compiler will implicitly generate deprecated copy special members (leading to polymorphic slicing). However in this case the compiler will not implicitly generate move special members, so copy will be used where a move would be possible.
protected copy/move member functions (your proposal)
With special copy/move member functions as default and protected, as in your example, you will prevent polymorphic copy which would otherwise had lead to slicing:
*B_p = *A_p; // will not compile, copy is protected in InterfaceClass
However, the compiler will explicitly generate a default copy assignment operator for InterfaceClass, and ImplementationClass will be able to implicitly generate its copy assignment operator:
B = A; //will compile and work correctly
So your approach seems the best and safest alternative
For destructor, if you want to make it both pure virtual and default, you can default it in implementation:
class InterfaceClass
{
// -- Destructors --
virtual ~InterfaceClass() = 0;
};
InterfaceClass::~InterfaceClass() = default;
It does not make much difference if the destructor is default or empty, though.
Now for the rest of your question.
Typically you should have copy constructor and assignment operator defaulted. This way, they don't prevent making default assignment operators and copy constructor in derived classes. Default implementation is correct, as there's no invariant to copy.
So if you want to implement easily Clone method, deleting copy constructor would harm:
class InterfaceClass
{
virtual InterfaceClass* Clone() = 0;
virtual ~InterfaceClass() = 0;
};
class ImplementationClass : public InterfaceClass
{
public:
// This will not work if base copy constructor is deleted
ImplementationClass(const ImplementationClass&) = default;
// Writing copy constructor manually may be cumbersome and hard to maintain,
// if class has a lot of members
virtual ImplementationClass* Clone() override
{
return new ImplementationClass(*this); // Calls copy constructor
}
};
Note also that default implementation of copy/move constructor would not be accidentally used against intention - as instances of abstract base class cannot be created. So you will always be copying derived classes, and they should define, if copying is legal or not.
However, for some classes making copies totally would not make sense, in this case it may be wise to prohibit copying/assigning in the very base class.
Tl;dr: it depend, but most likely you'd better leave them as default.
In general, if any of the big 3 special functions has none-[trivial/default] definition, the other 2 should be defined. If the 2 special move functions have none-[trivial-default] definition, then you need take care of all 5.
In the case of an interface with a nop defined dtor, you don't need bother defining the rest - unless for other reasons.
Even none-trivial definitions do not nessecitate a redefinition of other functions; only when some sort of resource management(e.g. memory, file, io, sync...) is involved, one need define the big 3(5).

C++11 virtual destructors and auto generation of move special functions

The rules for auto generating special move functions (constructor and assignment operator) in C++11 specify that no destructor can be declared. The logic is presumably that, if you need to do something special in destruction, that a move may not be safe.
However, for proper destructor calls in polymorphism, it is necessary to declare a base classes' destructor as virtual (otherwise deleting an instance of a sub class through a pointer of its base class will not properly chain the destructor).
I'm assuming, then, that even an empty destructor would prevent the compiler from automatically generating a special move functions. As in:
class Base {
virtual ~Base() { }
};
You can, however, default the destructor, as in:
class Base {
virtual ~Base() = default;
}
So question 1: Will this allow the compiler to auto generate special move functions?
There is a problem with the explicit default destructor, however. In at least the case of GCC 4.8.2, the signature is implicitly changed to noexcept. As in:
class Base {
virtual ~Base() = default; // compiler changes to:
// virtual ~Base() noexcept;
}
While I have no problem with noexcept in a destructor, this would break the following "client" code:
class Sub : public Base {
virtual ~Sub(); // this declaration is now "looser" because of no noexcept
}
So question 2 is more to the point: is there a way to allow auto generation of special move functions in C++11 and allow proper destructor chaining to sub classes (as described above), all without breaking subclass ("client") code?
No, a defaulted destructor is still considered user defined, so it will prevent the generation of move operations. Also declare the move operations default-ed to make the compiler generate them.
You need to only declare the move operations as default-ed in the base class. In the derived class, the destructor won't be user defined anymore (unless you explicitly say so), so the move operations won't be deleted.
So what I'd do is the following:
class Base
{
virtual ~Base() = default;
Base(Base&&) = default;
Base& operator=(Base&&) = default;
// probably need to think about copy operations also, as the move disables them
Base(const Base&) = default;
Base& operator=(const Base&) = default;
};
I highly recommend this talk by the person who contributed probably the most to the move semantics: http://www.slideshare.net/ripplelabs/howard-hinnant-accu2014
Or, if you can get your hands on, you should read the Item 17: Understand special member function generation from Scott Meyers' excellent book Effective Modern C++. This issue is excellently explained.
PS: I think you should think a bit more about your base classes. Most of the time, you should use abstract classes, so there will be no need to copy/move instances of them.
PSS: I think by default destructors are marked noexcept in C++11/14, so not explicitly specifying it shouldn't cause any problems:
Inheriting constructors and the implicitly-declared default
constructors, copy constructors, move constructors, destructors,
copy-assignment operators, move-assignment operators are all
noexcept(true) by default, unless they are required to call a function
that is noexcept(false), in which case these functions are
noexcept(false).