文章目录
The Semantics of Constructors
2.1 Default Constructor Construction
The C++ Annotated Reference Manual (ARM) [ELLIS90] tells us that “default constructors…are generated (by the compiler) where needed….”
Global objects are guaranteed to have their associated memory “zeroed out” at program start-up. Local objects allocated on the program stack and heap objects allocated on the free-store do not have their associated memory zeroed out; rather, the memory retains the arbitrary bit pattern of its previous use.
If there is no user-declared constructor for class X, a default constructor is implicitly declared…. A constructor is trivial if it is an implicitly declared default constructor….
A nontrivial default constructor is one that in the ARM’s terminology is needed by the implementation and, if necessary, is synthesized by the compiler( This synthesis, however, takes place only if the constructor actually needs to be invoked.). The next four sections look at the four conditions under which the default constructor is nontrivial.
1. Member Class Object with Default Constructor (user defined constructor)
An interesting question, then: Given the separate compilation model of C++, how does the compiler prevent synthesizing multiple default constructors, for example, one for file A.C and a second for file B.C? In practice, this is solved by having the synthesized default constructor, copy constructor, destructor, and/or assignment copy operator defined as inline
class Foo { public: Foo(), Foo( int ) ... };
class Bar { public: Foo foo; char *str; };
void foo_bar() {
Bar bar; // Bar::foo must be initialized here
if ( str ) { } ...
}
The synthesized default constructor here is nontrivial.
2. Base Class with Default Constructor
Similarly, if a class without any constructors is derived from a base class containing a default constructor, the default constructor for the derived class is considered nontrivial and so needs to be synthesized.
3. Class with a Virtual Function
A virtual function table is generated for that class. Within each class object, the vptr is synthesized to hold the address of the associated class vtbl. The compiler must initialize the vptr of each object.
4. Class with a Virtual Base Class
Virtual base class implementations vary widely across compilers.
class X { public: int i; };
class A : public virtual X { public: int j; };
class B : public virtual X { public: double d; };
class C : public A, public B { public: int k; };
// cannot resolve location of pa->X::i at compile-time
void foo( const A* pa ) { pa->i = 1024; }
main() {
foo( new A );
foo( new C );
// ...
}
the compiler cannot fix the physical offset of X::i accessed through pa within foo()
// possible compiler transformation
void foo( const A* pa ) { pa->__vbcX->i = 1024; }
The initialization of __vbcX (or whatever implementation mechanism is used) is accomplished during the construction of the class object.
++The Standard refers to these as implicit, nontrivial default constructors.++
- Common misunderstandings:
-
That a default constructor is synthesized for every class that does not define one
-
That the compiler-synthesized default constructor provides explicit default initializers for each data member declared within the class
-
2.2 Copy Constructor
Bitwise Copy Semantics—Not!
There are four instances:
- When the class contains a member object of a class for which a copy constructor exists (either explicitly declared by the class designer, as in the case of the previous String class, or synthesized by the compiler, as in the case of class Word)
- When the class is derived from a base class for which a copy constructor exists (again, either explicitly declared or synthesized)
- When the class declares one or more virtual functions
- When the class is derived from an inheritance chain in which one or more base classes are virtual
Resetting the Virtual Table Pointer
when a class declare one or more virtual fucntions:
- A virtual function table that contains the address of each active virtual function associated with that class (the vtbl) is generated.
- A pointer to the virtual function table is inserted within each class object (the vptr).
To reset vptr, compiler will synthesize a copy constructor in order to properly initialize the vptr.
Handling the Virtual Base Class Subobject
Each implementation’s support of virtual inheritance involves the need to make each virtual base class subobject’s location within the derived class object available at runtime. Maintaining the integrity of this location is the compiler’s responsibility. Bitwise copy semantics could result in a corruption of this location, so the compiler must intercede with its own synthesized copy constructor.
2.3 Program Transformation Semantics
Argument Initialization
The Standard states (Section 8.5) that passing a class object as an argument to a function (or as that function’s return value) is equivalent to the following form of initialization:
X xx = arg;
an invocation of the form
X xx;
// ...
foo( xx );
would be transformed as follows:
// Pseudo C++ code
// compiler generated temporary
X __temp0;
// compiler invocation of copy constructor
__temp0.X::X ( xx );
// rewrite function call to take temporary
foo( __temp0 ); //The declaration of foo() therefore must also be transformed void foo( X& x0 )
An alternative implementation is to copy construct the actual argument directly onto its place within the function’s activation record on the program stack. Prior to the return of the function, the local object’s destructor, if defined, is applied to it. The Borland C++ compiler, for example, implements this strategy.
Return Value Initialization
- Add an additional argument of type reference to the class object. This argument will hold the copy constructed “return value.”
- Insert an invocation of the copy constructor prior to the return statement to initialize the added argument with the value of the object being returned.
X bar()
{
X xx;
// process xx ...
return xx;
}
transformed to
// function transformation to reflect
// application of copy constructor
// Pseudo C++ Code
void
bar( X& __result )
{
X xx;
// compiler generated invocation
// of default constructor
xx.X::X();
// ... process xx
// compiler generated invocation
// of copy constructor
__result.X::X( xx );
return;
}
X xx = bar();
transform to :
X xx; //Note: no constructor applied
bar( xx );
______________
bar().memfunc();
transformed to:
// compiler generated temporary
X __temp0;
( bar( __temp0 ), __temp0 ).memfunc();
Optimization
NRV: it is possible for the compiler itself to optimize the function by substituting the result argument for the named return value.
void
bar( X &__result )
{
// default constructor invocation
// Pseudo C++ Code
__result.X::X();
// ... process in __result directly
return;
}
wheather or not to use NRV depends on compiler.
Member Initialization List
the compiler iterates over and possibly reorders the initialization list to reflect the declaration order of the members. It inserts the code within the body of the constructor prior to any explicit user code.