I am reading textbook "C++ Primer Plus, Prata"
A paragraph in chapter 10 catches my eye and confuses me.
Ch.10 Objects and classes:
It says
If you don't provide one, the compiler implicitly declares a default constructor and,...
I thought it should be
If you don't provide destructor, the compiler implicitly declares a default destructor and,...
Is the paragraph correct?
How should I explain that correctly?
Thank you
The "one" part is correct. That's just a nuance of English grammar where you can refer to something in a dependent clause that comes later in the sentence. Think of it like a forward declaration! The "default constructor" part is actually a typo: it should be "default destructor", like you originally thought.
It should say this:
Because a destructor is called automatically when a class object expires, there ought to be a destructor. If you don't provide one [a destructor], the compiler implicitly declares a default destructor and, if it detects code that leads to the destruction of an object, it provides a definition for the destructor.
Here, "one" refers to "a destructor," which comes later in the sentence. Another key to understanding the sentence is keeping in mind the distinction between declaring a function and defining a function. The compiler always declares an implicit destructor if you don't provide one, but it only defines it if it needs it (that is, if that destructor is going to be called).
What makes it all the more confusing (and probably what led to the typo) is that all of this is equally true for constructors.
Let's see if we can improve on the paragraph:
Because the destructor will be called automatically when a class object goes out of scope, all classes must have a destructor. If you don't explicitly provide one, the compiler implicitly declares a default destructor. If the compiler detects code that leads to the destruction of an object, it also provides a default definition for the destructor. The same thing is true for constructors.
Related
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.
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.
I actually got the idea of this question when I was discussing on another question of mine (Member not zeroed, a clang++ bug?). That question is about C++11 value-initialization, but when I saw the C++03 value-initialization rule someone posted there, I am confused.
The value-initialization rule from C++03 is:
To value-initialize an object of type T means:
if T is a class type (clause 9) with a user-declared constructor (12.1), then the default constructor for T is called (and the
initialization is ill-formed if T has no accessible default
constructor);
if T is a non-union class type without a user-declared constructor, then every non-static data member and base-class component of T is
value-initialized;
if T is an array type, then each element is value-initialized;
otherwise, the object is zero-initialized
Please look at the second bullet which defines the value-initialize process for a type without user-declared constructor. This rule doesn't mention a constructor call. As we can see from the description of other cases of value-initialize or from the description of default-initialize, if constructor should be called, it will be explicitly mentioned in the text of the standard. I know there is certain initialization form that constructor doesn't get called (e.g. {}-initialization for aggregates), but should it be the case for value-initialize of non-union class type without a user-declared constructor? The implicitly declared constructor of such a type could easily be non-trivial. For example:
class A {
public:
virtual void f() {}
};
According to the rule in C++03, if the implicitly declared non-trivial constructor doesn't get called in the process of the value-initialization of an object of A, how does the vptr of the object get setup? (I know things related to vptr is all implementation-defined, but this doesn't change the major point I'm trying to make here.)
(Someone would argue that the absence of mentioning a constructor call in the rule doesn't mean constructor won't get called. OK. Let's say constructor will get called according to some other rule I may have overlooked, but since all members need to be value-initialized anyway, wouldn't that cause the members' constructors to be called more than once?)
Asking a question for C++03 when it's already C++11 everywhere may seem worthless. Yeah, that's a valid point. However, I think I could more or less learn something if I finally figure this out (whether I am wrong and why).
EDIT: Maybe I shouldn't have used vptr as an example. My point is, wouldn't skipping the call to a non-trivial constructor cause some potential problem for the validity of the object? After all, it's called non-trivial for a reason.
As far as the Standard is concerned, the constructor is not responsible for setting up the vtable. There is nothing responsible for setting up the vtable; vtables don't exist, as far as the Standard is concerned.
Rather, the vtable is a consequence of the other rules the compiler has to follow, relating to virtual function binding and such. So whether or not the constructor is called, the vtable's going to be set up somewhere, because otherwise the compiler will have trouble meeting its other obligations. That doesn't contradict the value-initialization rule; rather, it adds nuance to the practicalities of implementing the rule.
When a class X has no user-provided constructor, its default constructor does exactly what a constructor of the form X::X() {} would (C++03[class.ctor]§6). And as far as the standard is concerned, this is defined to perform default initialisation of all members and base class subobjects, and nothing else. So "calling the generated default constructor" is identical to "default-initialising all data members and base class subobjects."
So this actually does "less" than the value initialisation you quoted - as that value initialisation value-initialises all data members and base class subobjects. So it does all the constructor does, and more.
As far as implementation-specific things (like the vtable pointer) go, these are outside of the scope of the standard. It is a compiler's responsibility to make sure all of its implementation-specific mechanisms work regardless of constructor calls mandated by the standard.
The article Are destructors overloadable? talks about overloading the destructor.
This raised a question: Can a destructor have parameters?
I've never used or seen a destructor with parameters. I could not come up with an example of a reason to use parameters to the destructor.
Section §12.4 of C++0x draft n3290 has this to say about destructors:
Destructors
A special declarator syntax using an optional function-specifier (7.1.2) followed by ˜ followed by the destructor’s class name followed by an empty parameter list is used to declare the destructor in a class definition.
(emphasis added)
So no, destructors do not take parameters. (The 2003 standard has the exact wording of the above paragraph.)
No, is the simple answer. This would make automatic resource management a significant bitch, because you'd have to worry about what parameters the destructor took and where the hell you were going to get them from. What about in the case of exception- how would the compiler know what to pass your destructor?
No. You hardly ever call them directly anyway, so what would be the use.
The destructor is supposed to destroy the object, nothing more.
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.