When to make a destructor defaulted using =default? - c++

Although using =default for constructors is clear for me (i.e. forcing the compiler to create the default constructor while other ctors exist), I still cannot understand the difference between these two type of destructors:
Those that use =default
Those that are not defined explicitly and generated by compiler automatically.
The only thing that comes to my mind is that the group-1 destructors can be defined as virtual, but group-2 is always non-virtual. So, is that the only difference between them? Is there any scenarios that the compiler is not generating the destructor, but using =default forces the compiler to generate it?
p.s. I have checked lots of Qs in stackoverflow, but none of them answers my Q. Here are some relevant questions.
Difference between =default and {} ctos/destructors
Defaulting virtual destructors
Difference between =default and empty dtrs
Edit 1: This Q on SO, focuses on disabling the default move constructors, that can be considered as ONE of the items mentioned in the accepted answer.

(Several of the points below were already mentioned in comments or in the linked questions; this answer serves to organize and interrelate them.)
There are of course three ways to get a “simple destructor”:
struct Implicit {};
struct Empty {~Empty() {}};
struct Defaulted {~Defaulted()=default;};
Like a default (and not a copy or move) constructor, {} and =default; mean largely the same thing for destructors. The interesting properties of Defaulted are then those (combinations) that differ from both of the others.
Versus Empty the main difference is simple: the explicitly defaulted destructor can be trivial. This applies only if it is defaulted inside the class, so there is no difference between {} and =default; on an out-of-line definition. Similarly, being virtual removes any distinction, as does having any member or base class with a non-trivial destructor. There is also the distinction that an explicitly defaulted destructor can be implicitly defined as deleted. Both of these properties are shared with implicitly declared destructors, so we have to find a distinction from those as well.
Versus Implicit, an explicitly defaulted destructor suppresses move operations, can be declared private, protected, or noexcept(false), and in C++20 can be constrained (but not consteval). Very marginally, it can be declared constexpr to verify that it would be anyway. Declaring it inline doesn’t do anything. (It can also be out of line or virtual, but as stated above that can’t be a reason to use it.)
So the answer is “when you want a trivial (or potentially deleted) destructor that has other special properties”—most usefully, access control or noexcept status.

Related

Are there observable semantic differences between different implicitly generated functions?

I've been reading the C++ standard trying to understand if there are any observable differences between trivial, simple, and implicitly defined constructors/assignment operators/destructors. From my current understanding there doesn't seem to be a difference, but that seems odd, why spend so much time defining them when it doesn't matter?
As a particular concrete example, consider copy constructors.
A trivial copy constructor copies all fields and base classes field-by-field if all fields and base classes are trivial.
Otherwise, the implicitly generated copy constructor: "performs full member-wise copies of bases and non-static members in initialization order".
If I understand it correctly, if a class has all trivial bases and fields but has a defaulted copy-constructor, then the defaulted copy-constructor will do exactly the same thing as the trivial constructor. Not even the initialization order seems to be relevant here because the fields are all disjoint (since trivial implies the absence of virtual base classes).
Is there ever an instance when a trivial copy-constructor will do something different than an explicitly defaulted copy constructor?
Generally, the same logic seems to hold for other constructors and destructors as well. The argument for assignment is a little bit more complex due to the potential for data races, but it seems like all of those would be undefined behavior by the standard if the class was actually trivial.
Not exactly about the behavior of the actual special member function per-se*, but consider the following:
struct Normal
{
int a;
};
static_assert(std::is_trivially_move_constructible_v<Normal>);
static_assert(std::is_trivially_copy_constructible_v<Normal>);
static_assert(std::is_copy_constructible_v<Normal>);
This all seems well and good.
Now consider the following:
struct Strange
{
Strange() = default;
Strange(Strange&&) = default;
};
static_assert(std::is_trivially_move_constructible_v<Strange>);
static_assert(!std::is_trivially_copy_constructible_v<Strange>);
static_assert(!std::is_copy_constructible_v<Strange>);
Hmm. The mere act of explicitly defaulting a move constructor disallows the object from being copy constructible!
Why is this?
Because, even though the compiler is still defining the move constructor for Strange, it's still a user-declared move constructor, which disables the generation of the copying special member functions.
The standard is very finicky about which special member functions get generated when you have user-declared versions of them, so it's best to stick with the Rule of Five or Zero.
Live Demo
Extra credit
By explicitly defaulting a default constructor for Strange, it is no longer an aggregate type (whereas Normal is). This opens up a whole different can of worms about initialization.
*Because as far as I know, the behavior of an explicitly defaulted special member function is identical to the trivial version of that function (or rather, it's the other way around). However, I have to note one peculiarity about the standard wording; when discussing the implicitly declared copy constructor, the standard neglects to say "implicitly declared as defaulted" like it does for the default and move constructors. I believe this to be a minor typo.
As a particular concrete example, consider copy constructors.
A trivial copy constructor copies all fields and base classes field-by-field if all fields and base classes are trivial.
Otherwise, the implicitly generated copy constructor: "performs full member-wise copies of bases and non-static members in initialization order".
The Standard specifies the behavior of implicitly defined special functions in just one place each. For example, [class.copy.ctor]/11 defines whether or not a copy or move constructor qualifies as "trivial". [class.copy.ctor]/14, which contains the quote about "performs a memberwise copy/move", applies whether or not the copy or move constructor is "trivial". When paragraph 11 talks about "the constructor selected" to move a base or member, it's referring to the choices made by the (potential) definition described by paragraph 14.
So yes, being trivial doesn't make a difference for how the class object is initialized. Instead, it makes a difference for other uses of an object of that type, sometimes to allow treating the class type in a more "C-like" way. This isn't a complete listing, but some notable Standard rules which reference triviality:
Implicitly declared special member functions of a union or class containing an anonymous union are defined as deleted if the corresponding special member of any class-type variant member is not trivial.
It's valid to copy objects of a trivially copyable class (see [class.prop]/1) byte by byte. ([basic.types]/2-3).
It's always valid to pass an object of class type through a C-style variadic function's ... if the copy constructor, the move constructor (if any), and the destructor are all trivial. Otherwise, passing an object of the class type is conditionally supported. ([expr.call]/12)
Of course, the std::is_trivially_* traits can tell the difference.

Does access control matter for deleted constructors?

Is there a difference in behavior between whether an explicitly deleted constructor is public, protected or private?
For example, a non copyable class would have a deleted copy constructor (and deleted copy assignment). The constructor would not be available to neither subclasses (or friends) nor to outside users of the class, because it doesn't exist, regardless of its access control.
The only difference I see would be in what manner different scopes would see that the copy constructor doesn't exist - whether it is just not there (as far as that scope knows) or it is explicitly deleted.
Now, it might be beneficial to have the best formal interface for the class - that is, if everyone should know that the class is non-copyable, it should be publicly deleted. Compiler error messages might also be more informative. But other than that - would there be any actual observable difference in the class behavior? That is, something one could do with a class which has its deleted constructor with access X which he couldn't do if that constructor had access Y?
Since it's overload resolution that makes the program ill-formed in this case, and not access specifiers (which are checked later), there is no difference in outcome. The compiler will always complain that a deleted function was picked.
But since the idiom before C++11 was "declare but not define a private copy c'tor to disable copying", I would consider it going along with the same idiom, and therefore favorable. You are using the "old slang" with some new language to describe the same thing, except better.

When to use =default vs =delete

To my understand these semantics are used only with the copy constructor, moving constructor, copy assignment, moving assignment, and the destructor. Using = delete is for prohibiting the use of one of the functions, and that = default is used if you want to be explicit to the compiler on where to use the defaults for these functions.
What are the best practices when using these keywords while making a class? Or rather how do I keep mindful of these when developing a class?
For example, if I don't know whether I'll use one of these functions, is it better to prohibit it with delete or allow it and use default?
Good question.
Also important: Where to use = default and = delete.
I have somewhat controversial advice on this. It contradicts what we all learned (including myself) for C++98/03.
Start your class declaration with your data members:
class MyClass
{
std::unique_ptr<OtherClass> ptr_;
std::string name_;
std::vector<double> data_;
// ...
};
Then, as close as is practical, list all of the six special members that you want to explicitly declare, and in a predictable order (and don't list the ones you want the compiler to handle). The order I prefer is:
destructor // this tells me the very most important things about this class.
default constructor
copy constructor // I like to see my copy members together
copy assignment operator
move constructor // I like to see my move members together
move assignment operator
The reason for this order is:
Whatever special members you default, the reader is more likely to understand what the defaults do if they know what the data members are.
By listing the special members in a consistent place near the top, and in a consistent order, the reader is more likely to quickly realize which special members are not explicitly declared &dash; and thus either implicitly declared, or not do not exist at all.
Typically both copy members (constructor and assignment) are similar. Either both will
be implicitly defaulted or deleted, explicitly defaulted or deleted, or explicitly supplied. It is nice to confirm this in two lines of code right next to each other.
Typically both move members (constructor and assignment) are similar...
For example:
class MyClass
{
std::unique_ptr<OtherClass> ptr_;
std::string name_;
std::vector<double> data_;
public:
MyClass() = default;
MyClass(const MyClass& other);
MyClass& operator=(const MyClass& other);
MyClass(MyClass&&) = default;
MyClass& operator=(MyClass&&) = default;
// Other constructors...
// Other public member functions
// friend functions
// friend types
// private member functions
// ...
};
Knowing the convention, one can quickly see, without having to examine the entire class declaration that ~MyClass() is implicitly defaulted, and with the data members nearby, it is easy to see what that compiler-declared and supplied destructor does.
Next we can see that MyClass has an explicitly defaulted default constructor, and with the data members declared nearby, it is easy to see what that compiler-supplied default constructor does. It is also easy to see why the default constructor has been explicitly declared: Because we need a user-defined copy constructor, and that would inhibit a compiler-supplied default constructor if not explicitly defaulted.
Next we see that there is a user-supplied copy constructor and copy assignment operator. Why? Well, with the data members nearby, it is easy to speculate that perhaps a deep-copy of the unique_ptr ptr_ is needed. We can't know that for sure of course without inspecting the definition of the copy members. But even without having those definitions handy, we are already pretty well informed.
With user-declared copy members, move members would be implicitly not declared if we did nothing. But here we easily see (because everything is predictably grouped and ordered at the top of the MyClass declaration) that we have explicitly defaulted move members. And again, because the data members are nearby, we can immediately see what these compiler-supplied move members will do.
In summary, we don't yet have a clue exactly what MyClass does and what role it will play in this program. However even lacking that knowledge, we already know a great deal about MyClass.
We know MyClass:
Holds a uniquely owning pointer to some (probably polymorphic) OtherClass.
Holds a string serving as a name.
Holds a bunch of doubles severing as some kind of data.
Will properly destruct itself without leaking anything.
Will default construct itself with a null ptr_, empty name_ and data_.
Will copy itself, not positive exactly how, but there is a likely algorithm we can easily check elsewhere.
Will efficiently (and correctly) move itself by moving each of the three data members.
That's a lot to know within 10 or so lines of code. And we didn't have to go hunting through hundreds of lines of code that I'm sure are needed for a proper implementation of MyClass to learn all this: because it was all at the top and in a predictable order.
One might want to tweak this recipe say to place nested types prior to the data members so that the data members can be declared in terms of the nested types. However the spirit of this recommendation is to declare the private data members, and special members, both as close to the top as practical, and as close to each other as practical. This runs contrary to advice given in the past (probably even by myself), that private data members are an implementation detail, not important enough to be at the top of the class declaration.
But in hindsight (hindsight is always 20/20), private data members, even though being inaccessible to distant code (which is a good thing) do dictate and describe the fundamental behaviors of a type when any of its special members are compiler-supplied.
And knowing what the special members of a class do, is one of the most important aspects of understanding any type.
Is it destructible?
Is it default constructible?
Is it copyable?
Is it movable?
Does it have value semantics or reference semantics?
Every type has answers to these questions, and it is best to get these questions & answers out of the way ASAP. Then you can more easily concentrate on what makes this type different from every other type.
Also, using =default instead of a hand-rolled one keeps the POD nature of the class, as it is described here in detail: Default constructors and POD
You often see = default when you are trying to maintain the rule of 5 to ensure the special member functions behave as you intend them to, and so that the reader of the class can see that you did consider how you wanted that function to behave.
You can use = delete if you intent to make something non-copyable or non-movable, for example. Though I have also seen people delete an inherited function if they do not want that specific derived class to have that function, though I'm not a huge fan of that since it tends to point towards poor architecture/design.

Is it safe to rely on an implicitly declared move constructor?

This is where I got most of this information: http://en.cppreference.com/w/cpp/language/move_constructor
Apparently these are the conditions for the implicitly generated move constructor to work:
there are no user-declared copy constructors
there are no user-declared copy assignment operators
there are no user-declared move assignment operators
there are no user-declared destructors
the implicitly-declared move constructor is not defined as deleted
if a user declared move constructor is present, it is still possible to still force the generation of the implicitly declared move constructor with the keyword default
My questions are:
Is it safe to rely on implicit automatic move constructor?
How do I check if it really worked instead of default copy constructor?
Finally, and most importantly, is it a good idea and why? Or is it always better to define my own?
I am more inclined to follow the rule of three and manually create a destructor, a copy and move constructor, and a copy and move assignment operator, but I'm just curious about this implicit one.
Here are the answers to your questions:
What do you mean with "safe"? When the rules apply, i.e., the subobjects are movable and you didn't do anything to stomp on the generation of the move constructor, it will be created and used when present. Note, however, that it is easy to have a non-movable subobject which will somewhat invisibly inhibit the creation of a move constructor.
To see if your class got a move constructor, just temporarily add an empty base logging when the copy and the move constructors are used and force the object to be moved/copied: it will log the correspondingly used constructor.
No code is generally better than any code.
1. Is it safe to rely on implicit automatic move constructor?
Nothing is safe to rely upon without testing (implicit or explicit).
2. How do I check if it really worked instead of default copy constructor?
Testing. See the example test below.
3. Finally, and most importantly, is it a good idea and why? Or is it
always better to define my own?
There are distinct (and growing) advantages to making your special members trivial. A trivial special member is one defined/supplied by the compiler. You can declare a trivial member with = default. Actually that last sentence is an exaggeration. If you declare a special member with = default, it won't for sure be trivial. That depends on your members and bases. But if you define a special member explicitly (as in C++98/03), then for sure it will not be trivial. If you have a choice between user-provided and trivial, prefer trivial.
Furthermore, you don't need to test if your type X has a move constructor. You need to test that if you move construct your X, that it has the right exception safety, and the right performance. If X::X(const X&) accomplishes that task, then so be it. In that case X::X(X&&) is not necessary.
If you expect that your type X will have a throwing copy constructor, and a much faster noexcept move constructor, here is a really nice test to confirm it is so:
static_assert(std::is_nothrow_move_constructible<X>::value,
"X should move construct without an exception");
Put this test right in your source/header. Now, no matter whether you implicitly, or explicitly declare or define your special members, you've got a concrete compile-time test that is practically zero cost. The static_assert generates zero code, and consumes a negligible amount of compile time.

destructors: triviality vs implicit definition

As I understand the standard, a trivial destructor is one which is implicitly declared and whose class has only base and non-static members with trivial destructors.
Given the recursivity of this definition, it seems to me that the only "recursion-stopping" condition is to find a base or non-static member with a non-implicitly declared destructor (i.e. user declared).
If that's right, that should mean that a trivial destructor is one which "doesn't have to do anything" and hence it will be declared (implicitly) but not defined.
Saying it in another way: is it correct to say that an implicitly defined destructor (that is "it does something") cannot be trivial as per the standard definition?
Sorry for the kind of silly question, but I'd like to clarify things a bit in my head...
No. An implicitly defined, trivial destructor is by definition trivial :) The difference between the declare and define thingy is that in order for the compiler to even see that a destructor is available, there must always a declaration. So if you don't provide one, it will implicitly provide one.
But now, it will also define one, if that is needed (if an object of that class type is destroyed). In any case, it has to do something: It needs to call the destructors of all its members and base classes. A simple example which illustrates the effect of implicitly defining a destructor:
struct a {
private:
~a();
};
struct bug {
// note: can't be destructed
a a_;
};
As soon as you try to create a local object of bug, the compiler will signal an error, because it yields a definition of a destructor for bug, which tries to call the not accessible destructor of a.
Now, i think triviality of destructors/constructors are mostly used to put constraints on your program. Objects having non-trivial versions of them can't be put in unions, for example. On the other side, you can delete an object having incomplete type, provided it has a trivial destructor. Note that if your program can't decide whether or not a trivial destructor was actually defined, the compiler is allowed to omit defining it. That's the so-called as-if rule. The compiler has to behave as-if it's Standard compliant - optimizations do not matter as long as they don't change the meaning of a program.
Your wording is a bit unfortunate. E.g. the recursion of course also ends when you run out of members and base classes. Those wording problems also seem to get you more confused.
Anyway, all implicitly-declared destructors, whether they are trivial or not, are defined if and only if they are used. Used is a specific term here. A destructor of type T is used whenever the lifetime of a T object ends.
Trivial destructors exist because C programmers put structs in unions. This code should remian legal in C++, so the notion of a trivial destructor was invented for C++. All C structs have trivial destructors, when compiled as C++.
Consider these two classes:
class A {
};
class B {
private:
A obj;
};
The destructors of both these classes implicitly defined. Yet, at the same time, both of them are trivial by the standard definition.