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.
Related
I was going through a code implementation where the intention was to not let anyone make objects of a particular class. Here is the code snippet:
class CantInstantiate
{
CantInstantiate();
CantInstantiate(const CantInstantiate&);
...
};
Is it really required to make the copy constructor private undefined if the default constructor is already made private undefined (provided there is no other constructor)? What is the benefit of preventing copy of an object when we don't have an original object in the first place? Please explain. Thanks in advance.
If the intent is to prevent making instances of a class then you need to ensure you can't call any constructor. If you don't explicitly prohibit copy construction then you're leaving it up to the compiler to decide whether to include one or not, based on the conditions for causing a deleted implicitly-declared copy constructor which are:
- T has non-static data members that cannot be copied (have deleted, inaccessible, or ambiguous copy constructors);
- T has direct or virtual base class that cannot be copied (has deleted, inaccessible, or ambiguous copy constructors);
- T has direct or virtual base class with a deleted or inaccessible destructor;
- T is a union-like class and has a variant member with non-trivial copy constructor;
- T has a data member of rvalue reference type;
- T has a user-defined move constructor or move assignment operator (this condition only causes the implicitly-declared, not the defaulted, copy constructor to be deleted).
So if we assume that you have a class A that attempts to prevent itself being constructed by only prohibiting default construction and not including anything in the list above, it's possible to exploit the implicit copy constructor to gain an instance of A, or even to inherit from it. For example:
class A
{
A(){};
};
struct B : public A {
B() : A(*a)
{}
A* a;
};
B b;
A a (*static_cast<A*>(nullptr));
Now admittedly the code above could produce unexpected behaviour and any decent compiler would produce warnings if you attempted to do it but it does compile. The practicality of such an approach would depend entirely upon what else was declared within A...
I would argue that if the idea is to stop creation then you need to ensure that all methods of construction are prevented. Therefore, it's a much better approach to explicitly delete the: default constructor, copy constructor and move constructor. This sends a much clearer statement of intent and I believe it should prevent all methods of construction.
class CantInstantiate
{
public:
CantInstantiate() = delete;
CantInstantiate(const CantInstantiate&) = delete;
CantInstantiate(CantInstantiate&&) = delete;
...
};
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.
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).
In C++11, a polymorphic class (one with virtual member methods) should/must have a virtual destructor (so that delete on a base-class pointer does the expected). However, declaring an destructor explicitly deprecates the implicit generation of the copy constructor (though this may not be widely implemented by compilers) and hence also of the default constructor. Thus, for any polymorphic class to not be deprecated it must have these members
virtual ~polymorphic_class() = default;
polymorphic_class() = default;
polymorphic_class(polymorphic_class const&) = default;
explicitly defined, even if they are trivial. Am I correct? (Isn't this annoying?) What is the logic behind this? Is there any way to avoid that?
Am I correct?
Yes, as per ForEveR's post.
Is there any way to avoid that?
Yes. Do this just once by implementing a base for all polymorphic classes (similarly to class Object in Java and D which is the root of all class hierarchies):
struct polymorphic {
polymorphic() = default;
virtual ~polymorphic() = default;
polymorphic(const polymorphic&) = default;
polymorphic& operator =(const polymorphic&) = default;
// Those are not required but they don't harm and are nice for documentation
polymorphic(polymorphic&&) = default;
polymorphic& operator =(polymorphic&&) = default;
};
Then, any class publicly derived from polymorphic will have a implicitly declared and defined (as defaulted) virtual destructor unless you declare one yourself.
class my_polymorphic_class : public polymorphic {
};
static_assert(std::is_default_constructible<my_polymorphic_class>::value, "");
static_assert(std::is_copy_constructible <my_polymorphic_class>::value, "");
static_assert(std::is_copy_assignable <my_polymorphic_class>::value, "");
static_assert(std::is_move_constructible <my_polymorphic_class>::value, "");
static_assert(std::is_move_assignable <my_polymorphic_class>::value, "");
What is the logic behind this?
I can't be sure. What follows are just speculations.
In C++98/03, the Rule of Three says that if a class needs either the copy constructor, the copy assignment operator or the destructor to be user defined, then it probably needs the three to be user defined.
Obeying the Rule of Three is a good practice but this is nothing more than a guideline. The Standard doens't force it. Why not? My guess is that people realized this rule only after the publication of the Standard.
C++11 introduced move constructor and move assignment operator, turning the Rule of Three into the Rule of Five. With benefit of hindsight, the committee wanted to enforce the Rule of Five. The idea was: if either of the five special functions is user declared then the others, but the destructor, won't be implicitly defaulted.
However, the committee didn't want to break virtually every C++98/03 code by enforcing this rule and then, decided to do it only partially:
If either the move constructor or the move assignment operator is user declared then other special functions, but the destructor, will be deleted.
If either of the five special functions is user declared then the move constructor and move assignment operators won't be implicitly declared.
In the case of C++98/03 well formed code, neither a move constructor nor a move assignment operator is ever user declared then, rule 1 doesn't apply. Hence when compiled with a C++11 compliant compiler C++98/03 well formed code doesn't fail to compile as a consequence of this rule. (If it does, it's for some other reasons.)
In addition, under rule 2 the move constructor and move assignment operator are not implicitly declared. This doesn't break C++98/03 well formed code either because they never expected the declaration of move operations anyway.
The deprecation mentioned in the OP and quoted in ForEveR's post suggests a possible enforcement of the Rule of Five by a future Standard. Be prepared!
You are correct, that should be true by standard in future, but now it's only deprecated, so every compiler should support implicitly-declared copy constructor, when destructor is virtual now.
n3376 12.8/7
If the class definition does not explicitly declare a copy constructor, one is declared implicitly. If the class
definition declares a move constructor or move assignment operator, the implicitly declared copy constructor
is defined as deleted; otherwise, it is defined as defaulted (8.4). The latter case is deprecated if the class has
a user-declared copy assignment operator or a user-declared destructor.
And it seems to me, that you cannot make any workaround for this.
struct Base{
Base(Base &){} // suppress default constructor
};
struct Derived : Base{
};
int main(){
Derived d;
}
The code shown gives error because the default constructor (implicit) of 'Base' is suppressed. Indeed the standard says in $12.1 "If there is no user-declared constructor for class X, a default constructor is implicitly declared."
There are three things:
a) Does the standard say anywhere that
if the user declared constructor is
present in a class, the default
constructor (implicit) is suppressed. It is bascically the above phrased negatively or is it once again implied :)?
b) Why is it that way?
c) Why the same rules do not apply for the default destructor?
I think that a) is sufficiently clearly implied by your quote.
As for “why” – quite simple: a default constructor is not always meaningful; if there were no way to suppress it, this would weaken C++ substantially.
As for c), a class without destructor (no “default”, just plain destructor) is simply not meaningful.
a) Does the standard say anywhere that
if the user declared constructor is
present in a class, the default
constructor (implicit) is suppressed.
It is bascically the above phrased
negatively or is it once again implied
:)?
Yes, that is the meaning
b) Why is it that way?
Most likely, if you have a user-defined constructor, it means special work needs to be done to initialize the object. It makes sense to disable the implicitly generated default constructor in such a case, because it likely won't do any of the special work.
c) Why the same rules do not apply for the default destructor?
Well, perhaps it would make sense for the language to enforce the "rule of three" (if you define one of copy constructor, assignment operator or destructor, chances are you need to implement all three), but it just doesn't.
Perhaps the rationale is that there are several ways to initialize a class, but assignment and destruction often works the same way (memberwise assignment, run destructor of all members).
The shortest answer is because you declared a constructor for the class Base, no default constructor is created (thus the suppression). You cannot initialize Derived because Derived has no default constructor to call on class Base. (this is because the default constructor of derived that is generated for you can only construct it's parent class with the default constructor)