Default Constructor
构建
ARM 中指出: Default Constructor 在需要的时候被编译产生出来。这里要注意的是 在需要的时候 这个关键,这种需要分为程序的需要和编译器的需要,如果是程序的需要,那么 Default Constructor 是程序员的责任。在 C++ 标准中指出 nontrivial default constructor 就是在 ARM 中指出的编译器需要的那种 default constructor 。
ARM 中指出: Default Constructor 在需要的时候被编译产生出来。这里要注意的是 在需要的时候 这个关键,这种需要分为程序的需要和编译器的需要,如果是程序的需要,那么 Default Constructor 是程序员的责任。在 C++ 标准中指出 nontrivial default constructor 就是在 ARM 中指出的编译器需要的那种 default constructor 。
四种
nontrivial default constructor
带有default Constructor的Member Class Object
如果一个类,如 CA ,没有 default constructor ,但他有一个对象类型的成员变量,那么这个类的隐含默认构造函数是一个 nontrivial default constructor ,编译器会自动为该类生成构造函数,构造函数生成的时机是在这个构造函数真正被调用的时候,生成的是 inline 类型的代码,这样可以防止一个程序中多处调用类 CA 的构造函数而生成多个构造函数。如果类 CA 已经定义了默认的构造函数,那么编译器就不会合成一个构造函数,而仅仅在程序员已经定义好的默认构造函数中插入需要合成的代码,即各个成员对象的默认构造函数,确保在调用程序员定义的代码之前先调用编译器生成的代码。如果 CA 中含有多个对象类型的变量,那么编译器按照这些对象在类中的申明顺序插入各个对象的默认构造函数,然后才是程序员的代码。
如果一个类,如 CA ,没有 default constructor ,但他有一个对象类型的成员变量,那么这个类的隐含默认构造函数是一个 nontrivial default constructor ,编译器会自动为该类生成构造函数,构造函数生成的时机是在这个构造函数真正被调用的时候,生成的是 inline 类型的代码,这样可以防止一个程序中多处调用类 CA 的构造函数而生成多个构造函数。如果类 CA 已经定义了默认的构造函数,那么编译器就不会合成一个构造函数,而仅仅在程序员已经定义好的默认构造函数中插入需要合成的代码,即各个成员对象的默认构造函数,确保在调用程序员定义的代码之前先调用编译器生成的代码。如果 CA 中含有多个对象类型的变量,那么编译器按照这些对象在类中的申明顺序插入各个对象的默认构造函数,然后才是程序员的代码。
带有default constructor 的基类
如果一个类 CD 没有任何 constructor ,而这个类从带有默认构造函数的基类 CB 继承,那么我们认为 CD 的 default constructor 是 nontrivial 的,因此他的 default constructor 需要被合成出来,合成以后的 default constructor 它将按照申明的顺序调用 CB 的 default constructor 。对 CD 来说这个合成出的构造函数同显式定义的构造函数没有区别。
如果 CD 提供了构造函数,但唯独没有 default constructor ,那么编译器会在现有的每个构造函数中插入 CB 的默认构造函数,而不会重新合成 default constructor 。如果同时存在带有 default constructor 的对象成员存在,这些对象成员的 default constructor 会在所有基类的 default constructor 调用之后被调用。
如果一个类 CD 没有任何 constructor ,而这个类从带有默认构造函数的基类 CB 继承,那么我们认为 CD 的 default constructor 是 nontrivial 的,因此他的 default constructor 需要被合成出来,合成以后的 default constructor 它将按照申明的顺序调用 CB 的 default constructor 。对 CD 来说这个合成出的构造函数同显式定义的构造函数没有区别。
如果 CD 提供了构造函数,但唯独没有 default constructor ,那么编译器会在现有的每个构造函数中插入 CB 的默认构造函数,而不会重新合成 default constructor 。如果同时存在带有 default constructor 的对象成员存在,这些对象成员的 default constructor 会在所有基类的 default constructor 调用之后被调用。
带有一个Virtual Function的类
无论类继承得到或者是类内部本身申明了 Virtual Function ,编译器在编译过程中记录两个内容: virtual function table ( vtbl ),存放类的 virtual function 地址;在每个类对象中,额外的指针 vptr 被产生,指向 vtbl 。编译器对于这样的类的每个构造函数会插入相应的代码取保在每个对象内部插入 vptr ,对于未定义 default constructor 的类,编译器会合成一个构造函数来完成这些工作。
无论类继承得到或者是类内部本身申明了 Virtual Function ,编译器在编译过程中记录两个内容: virtual function table ( vtbl ),存放类的 virtual function 地址;在每个类对象中,额外的指针 vptr 被产生,指向 vtbl 。编译器对于这样的类的每个构造函数会插入相应的代码取保在每个对象内部插入 vptr ,对于未定义 default constructor 的类,编译器会合成一个构造函数来完成这些工作。
带有virtual基类的类
基类对象元素的在子类的位置必须确保在执行期必须准备好,即运行时必须确定。各种编译器的实现方法不同,但目标是一致的。
基类对象元素的在子类的位置必须确保在执行期必须准备好,即运行时必须确定。各种编译器的实现方法不同,但目标是一致的。
总结
:以上四种情况,编译器会合成构造函数,除这些情况之外,对于那些没有申明任何类型构造函数的类,我们仅仅能说他有
implicit trivial default constructor
,实际上并不是被合成出来的。
Copy Constructor
的构建
有三种情况下会调用默认构造函数:
1. 显式调用 : class X { ... }; X x; X xx = x; 在这里 xx 显式调用了拷贝构造函数
2. 对象作为形式参数传递到函数,该参数未申明为引用或者指针
3. 对象作为函数的返回值返回
1. 显式调用 : class X { ... }; X x; X xx = x; 在这里 xx 显式调用了拷贝构造函数
2. 对象作为形式参数传递到函数,该参数未申明为引用或者指针
3. 对象作为函数的返回值返回
Default Memberwise Initialization
如果一个类没有提供构造函数,则如果用一个对象初始化另一个同类的对象是通过 Default Memberwise Initialization 来实现的。 Default Memberwise Initialization 拷贝每个内建的或者从基类继承来的数据成员。对于类对象型的成员变量,不会直接拷贝,而是递归地调用该类的 Memberwise Initialization 。
Bitwise Copy 语义
有四种情况下不会存在 Bitwise Copy 语义,即 nontrivial 的,如果没有定义 default copy function ,这时编译器需要合成出一个 copy function
1 类 A 的成员变量中包含一个对象类型(类 B )的对象,类 B 包含一个显式定义的拷贝构造函数,则称类 A 不具有 Bitwise Copy 语义:没有搞明白为什么
2. 类 A 的基类类 B 包含一个显式定义的拷贝构造函数,则称类 A 不具有 Bitwise Copy 语义: notrivial default constructor 语义决定的
3. 类 A 中定义了一个虚函数,或者从基类继承了一个虚函数,则称类 A 不具有 Bitwise Copy 语义:有 vptr 存在, Bitwise Copy 会将 vptr 替换
4. 类 A 的祖先是 Virtual 继承的,则称类 A 不具有 Bitwise Copy 语义:每个从 virtual 基类得到的子类,必须能准确定位到基类的位置,其他的没有明白
如果一个类没有提供构造函数,则如果用一个对象初始化另一个同类的对象是通过 Default Memberwise Initialization 来实现的。 Default Memberwise Initialization 拷贝每个内建的或者从基类继承来的数据成员。对于类对象型的成员变量,不会直接拷贝,而是递归地调用该类的 Memberwise Initialization 。
Bitwise Copy 语义
有四种情况下不会存在 Bitwise Copy 语义,即 nontrivial 的,如果没有定义 default copy function ,这时编译器需要合成出一个 copy function
1 类 A 的成员变量中包含一个对象类型(类 B )的对象,类 B 包含一个显式定义的拷贝构造函数,则称类 A 不具有 Bitwise Copy 语义:没有搞明白为什么
2. 类 A 的基类类 B 包含一个显式定义的拷贝构造函数,则称类 A 不具有 Bitwise Copy 语义: notrivial default constructor 语义决定的
3. 类 A 中定义了一个虚函数,或者从基类继承了一个虚函数,则称类 A 不具有 Bitwise Copy 语义:有 vptr 存在, Bitwise Copy 会将 vptr 替换
4. 类 A 的祖先是 Virtual 继承的,则称类 A 不具有 Bitwise Copy 语义:每个从 virtual 基类得到的子类,必须能准确定位到基类的位置,其他的没有明白
程序转化语义
显式的初始化
void foo_bar() {
X x1( x0 );
X x2 = x0;
X x3 = x( x0 );
// ...
}
初始化为
void foo_bar() {
X x1;
X x2;
X x3;
void foo_bar() {
X x1( x0 );
X x2 = x0;
X x3 = x( x0 );
// ...
}
初始化为
void foo_bar() {
X x1;
X x2;
X x3;
x1.X::X( x0 );
x2.X::X( x0 );
x3.X::X( x0 );
// ...
}
x2.X::X( x0 );
x3.X::X( x0 );
// ...
}
参数的初始化
X xx = arg;
void foo( X x0 );
如果调用 foo( xx ) 则程序转化为
X __tmp0;
__tmp0.X::X( xx );
foo( __tmp0 );
同时函数 foo 声明为 void foo( X& x0 ); 这样只需要构建一次 X 的实例
X xx = arg;
void foo( X x0 );
如果调用 foo( xx ) 则程序转化为
X __tmp0;
__tmp0.X::X( xx );
foo( __tmp0 );
同时函数 foo 声明为 void foo( X& x0 ); 这样只需要构建一次 X 的实例
返回值的初始化
X bar()
{
X xx;
// process xx ...
return xx;
} 转换为
void
bar( X& __result )
{
X xx;
X bar()
{
X xx;
// process xx ...
return xx;
} 转换为
void
bar( X& __result )
{
X xx;
// compiler generated invocation
// of default constructor
xx.X::X();
// of default constructor
xx.X::X();
// ... process xx
// compiler generated invocation
// of copy constructor
__result.X::X( xx );
// compiler generated invocation
// of copy constructor
__result.X::X( xx );
return;
}
用户层面做优化
定义一个计算用的构造函数
Named Return Value,将返回值定义为引用型的参数
}
用户层面做优化
定义一个计算用的构造函数
Named Return Value,将返回值定义为引用型的参数
初始化列表
对于以下几种情况,必须使用初始化列表才能通过编译
1. 初始化一个引用类型的成员变量
2. 初始化一个 const 类此能够的成员变量
3. 调用基类的构造函数,而基类的构造函数有一系列参数(书上的那个例子个人觉得很经典,值得学习,以往只知道这么使,但不知道为什么这样使)
4. 调用成员类对象的构造函数,而成员类的构造函数有一系列参数
对于以下几种情况,必须使用初始化列表才能通过编译
1. 初始化一个引用类型的成员变量
2. 初始化一个 const 类此能够的成员变量
3. 调用基类的构造函数,而基类的构造函数有一系列参数(书上的那个例子个人觉得很经典,值得学习,以往只知道这么使,但不知道为什么这样使)
4. 调用成员类对象的构造函数,而成员类的构造函数有一系列参数
注意
1. 初始化列表的初始化顺序是按照在类的声明中出现的顺序初始化的,并不是按照初始化列表中的顺序初始化。
2. 在类的构造函数中调用类的成员函数是允许的,因为这个类对象的 this 指针已经被完全构建
1. 初始化列表的初始化顺序是按照在类的声明中出现的顺序初始化的,并不是按照初始化列表中的顺序初始化。
2. 在类的构造函数中调用类的成员函数是允许的,因为这个类对象的 this 指针已经被完全构建