Constructor semantics
In this chapter, the original book mainly analyzes the compiler's interference with the object construction process, that is, what the compiler does behind the object construction process.
This chapter focuses on the knowledge of default constructor and copy constructor.
Default Constructor
Some C + + books tell us that if we don't write any constructors, the compiler will generate a default constructor for us.
So, there are two problems in this view:
- Is that right?
- If that's true, what are the actions in the default constructor that the compiler generates for us?
Is this sentence correct?
This is generally true, but it is explained in more detail in cppreference:
Implicitly declared default constructor
If no user declared constructor is provided for a class type (struct, class, or union), the compiler will always declare a default constructor that is an inline public member of its class
Implicitly defined default constructor
If the implicitly declared default constructor is not defined as deprecated, it is defined by the compiler (that is, generate and compile the function body) when it is used ODR style, and it has exactly the same effect as the user-defined constructor with empty function body and empty initializer list. That is, it calls the default constructor of each base class and each non static member of this class.
Is there any evidence that the compiler actually implicitly defines a default constructor? Yes, consider one of the following classes:
class A {};
There is no member variable or member function in this class. The relevant information of this class is printed as follows:
// Clang - CC1 - fdump record layouts - emit llvm main.cpp command *** Dumping AST Record Layout 0 | class A (empty) | [sizeof=1, dsize=1, align=1, | nvsize=1, nvalign=1] *** Dumping IRgen Record Layout Record: CXXRecordDecl 0x55c6626abc70 <main.cpp:1:1, line:3:1> line:1:7 referenced class A definition |-DefinitionData pass_in_registers empty aggregate standard_layout trivially_copyable pod trivial literal has_constexpr_non_copy_move_ctor can_const_default_init | |-DefaultConstructor exists trivial constexpr defaulted_is_constexpr | |-CopyConstructor simple trivial has_const_param implicit_has_const_param | |-MoveConstructor exists simple trivial | |-CopyAssignment trivial has_const_param needs_implicit implicit_has_const_param | |-MoveAssignment exists simple trivial needs_implicit | `-Destructor simple irrelevant trivial needs_implicit |-CXXRecordDecl 0x55c6626abd88 <col:1, col:7> col:7 implicit class A |-CXXConstructorDecl 0x55c6626ac020 <col:7> col:7 implicit used constexpr A 'void () noexcept' inline default trivial | `-CompoundStmt 0x55c6626ac4b8 <col:7> |-CXXConstructorDecl 0x55c6626ac158 <col:7> col:7 implicit constexpr A 'void (const A &)' inline default trivial noexcept-unevaluated 0x55c6626ac158 | `-ParmVarDecl 0x55c6626ac268 <col:7> col:7 'const A &' `-CXXConstructorDecl 0x55c6626ac308 <col:7> col:7 implicit constexpr A 'void (A &&)' inline default trivial noexcept-unevaluated 0x55c6626ac308 `-ParmVarDecl 0x55c6626ac418 <col:7> col:7 'A &&' Layout: <CGRecordLayout LLVMType:%class.A = type { i8 } NonVirtualBaseLLVMType:%class.A = type { i8 } IsZeroInitializable:1 BitFields:[ ]>
The line of default constructor exists trial constexpr defaulted ﹣ is ﹣ constexpr proves that there is a default constructor in this class and it is trial, but we do not declare and define it, so the compiler automatically generates it for us.
2 what happens in the implicitly defined default constructor?
Before solving this problem, we need to know some additional knowledge first:
Default constructor is divided into trivial default constructor (trivial default constructor, i.e. no construction action) and non trivial default constructor (nontrivial default constructor)
When is the default constructor trivial? This is illustrated in cppreference:
The default constructor for class T is trivial when all of the following are true (a trivial default constructor is one that does nothing.) :
Constructor is not user supplied (that is, it is implicitly defined or preset in its first declaration)
T has no virtual member function
T has no virtual base class
T does not have a non static data member with a default initializer.
Each T's direct base class has a trivial default constructor
Non static members of each class type have a trivial default constructor
Note that the first point in the above condition also illustrates the fact that if a constructor is user-defined, it must be nontrivial
Back to the implicitly defined default constructor, what happened? This problem needs to be considered from two perspectives:
- When the compiler implicitly defines the default constructor as a trial default constructor, the trial default constructor does nothing
- When the compiler implicitly defines a non-trial default constructor, the non-trial default constructor calls the default constructors of each base class and each non-static member of the class.
Through the above, we have known that the compiler will implicitly define a default constructor in a specific case, and also know in which cases the generated default constructor is trivial. Let's discuss in detail in which case the generated default constructor of the compiler is non trivial.
-
member class object with default constructor
If a Class A does not have any constructors, but it contains a member object, and the member object has a non tertiary default constructor, then the default constructor implicitly defined by the compiler for this class is non tertiary. Why? Because the compiler must call the non trivial default constructor of member object in the default constructor generated for class A, otherwise the class a object cannot be constructed completely.
Still using code to prove:class B { private: int b; public: B() {} }; class A { private: B b; int a; };
Print out the relevant information of class A as follows:
*** Dumping AST Record Layout 0 | class B 0 | int b | [sizeof=4, dsize=4, align=4, | nvsize=4, nvalign=4] *** Dumping AST Record Layout 0 | class A 0 | class B b 0 | int b 4 | int a | [sizeof=8, dsize=8, align=4, | nvsize=8, nvalign=4] *** Dumping IRgen Record Layout Record: CXXRecordDecl 0x55ba37562c50 <main.cpp:1:1, line:6:1> line:1:7 referenced class B definition |-DefinitionData pass_in_registers standard_layout trivially_copyable has_user_declared_ctor can_const_default_init | |-DefaultConstructor exists non_trivial user_provided | |-CopyConstructor simple trivial has_const_param implicit_has_const_param | |-MoveConstructor exists simple trivial | |-CopyAssignment trivial has_const_param needs_implicit implicit_has_const_param | |-MoveAssignment exists simple trivial needs_implicit | `-Destructor simple irrelevant trivial |-CXXRecordDecl 0x55ba37562d68 <col:1, col:7> col:7 implicit referenced class B |-AccessSpecDecl 0x55ba37562df8 <line:2:2, col:9> col:2 private |-FieldDecl 0x55ba37562e38 <line:3:3, col:7> col:7 b 'int' |-AccessSpecDecl 0x55ba37562e88 <line:4:2, col:8> col:2 public |-CXXConstructorDecl 0x55ba37562f38 <line:5:3, col:8> col:3 used B 'void ()' | `-CompoundStmt 0x55ba37562ff8 <col:7, col:8> |-CXXDestructorDecl 0x55ba37563590 <line:1:7> col:7 implicit ~B 'void ()' inline default trivial noexcept-unevaluated 0x55ba37563590 |-CXXConstructorDecl 0x55ba375636c8 <col:7> col:7 implicit constexpr B 'void (const B &)' inline default trivial noexcept-unevaluated 0x55ba375636c8 | `-ParmVarDecl 0x55ba375637d8 <col:7> col:7 'const B &' `-CXXConstructorDecl 0x55ba37563878 <col:7> col:7 implicit constexpr B 'void (B &&)' inline default trivial noexcept-unevaluated 0x55ba37563878 `-ParmVarDecl 0x55ba37563988 <col:7> col:7 'B &&' Layout: <CGRecordLayout LLVMType:%class.B = type { i32 } NonVirtualBaseLLVMType:%class.B = type { i32 } IsZeroInitializable:1 BitFields:[ ]> *** Dumping IRgen Record Layout Record: CXXRecordDecl 0x55ba37563008 <main.cpp:8:1, line:12:1> line:8:7 referenced class A definition |-DefinitionData pass_in_registers standard_layout trivially_copyable | |-DefaultConstructor exists non_trivial | |-CopyConstructor simple trivial has_const_param implicit_has_const_param | |-MoveConstructor exists simple trivial | |-CopyAssignment trivial has_const_param needs_implicit implicit_has_const_param | |-MoveAssignment exists simple trivial needs_implicit | `-Destructor simple irrelevant trivial needs_implicit |-CXXRecordDecl 0x55ba37563128 <col:1, col:7> col:7 implicit class A |-AccessSpecDecl 0x55ba375631b8 <line:9:2, col:9> col:2 private |-FieldDecl 0x55ba375631f0 <line:10:3, col:5> col:5 b 'B' |-FieldDecl 0x55ba37563258 <line:11:3, col:7> col:7 a 'int' |-CXXConstructorDecl 0x55ba375634b0 <line:8:7> col:7 implicit used A 'void () noexcept(false)' inline default | |-CXXCtorInitializer Field 0x55ba375631f0 'b' 'B' | | `-CXXConstructExpr 0x55ba37592090 <col:7> 'B' 'void ()' | `-CompoundStmt 0x55ba375920e8 <col:7> |-CXXConstructorDecl 0x55ba375639f8 <col:7> col:7 implicit constexpr A 'void (const A &)' inline default trivial noexcept-unevaluated 0x55ba375639f8 | `-ParmVarDecl 0x55ba37591e30 <col:7> col:7 'const A &' `-CXXConstructorDecl 0x55ba37591ec8 <col:7> col:7 implicit constexpr A 'void (A &&)' inline default trivial noexcept-unevaluated 0x55ba37591ec8 `-ParmVarDecl 0x55ba37591fd8 <col:7> col:7 'A &&' Layout: <CGRecordLayout LLVMType:%class.A = type { %class.B, i32 } NonVirtualBaseLLVMType:%class.A = type { %class.B, i32 } IsZeroInitializable:1 BitFields:[ ]>
DefaultConstructor exists non_trivial user_provided
This line indicates that class B is a user-defined default constructor, which is non trivial.
DefaultConstructor exists non_trivial
The second line indicates that the default constructor of class a also exists and is non trivial. In this non trivial default constructor, the compiler will call the default constructor of class B to initialize the b component of class A's object, but not a (in the compiler's view, initializing b is its work, but initializing a is the programmer's work)
|-CXXConstructorDecl 0x55ba375634b0
<><><><><>''<><>''<><>'&'<>'&'<>'&&'<>'&&'<>''<><><><><>''<>''<><>''''''<>''''<><>''''<>''><>''<>''<>'&'<>'&'<>'&&'<>'&&'<>
<>