Q1:什么时候编译器会合成默认构造函数?
Wrong idea: 当类中无构造函数时
①.若数据成员为类类型,且该类类型具有默认构造函数,则将调用其默认构造函数
②.若数据成员为内置类型,则按照系统的初始化规则进行初始化
**注:不满足四种情况时得到的是 implicit trival default constructors,实际上并不会合成出来。
Accurate idea: 当编译器需要时才会合成
①.当类有四种情况时,编译器会合成 implicit nontrivial default constructors。合成出的构造函数只能满 足编译器的需要,调用成员对象的默认构造函数或基类的默认构造函数或为每一个对象初始化虚函数机制或 虚基类机制而完成。
②.只有基类子对象与类对象成员才会被初始化,其他所有 nonstatic 成员将不会被初始化
1) Eg:
class A
{
public:
int x;
double str;
};
int main()
{
A a;
cout << a.str << endl;
cout << a.x << endl;
system("pause");
}
• 此时数据成员均为内置类型,将不会合成默认构造函数
• 在VS中运行改程序,将报错:使用未初始化的局部变量
• 在gcc中正常编译,输出结果未定义
• 此时初始化操作为:程序需要
2) Eg:
class A
{
public:
int x;
string str;
};
int main()
{
A a;
cout << a.str << endl;
cout << a.x << endl;
system("pause");
}
• 此时将会合成默认构造函数,但该函数仅调用了string 的默认构造函数对str 进行初始化
• x成员仍未初始化,但此时在VS中运行正确,str为空字符,x未定义
• 此时初始化操作为:编译器需要
Q2:以下四种情况下编译器将合成有用的默认构造函数 ——即编译器需要时
1) 类中含有"带有默认构造函数" 的类对象成员
2) 类的基类"带有默认构造函数"
3) 带有虚函数的类
4) 带有虚基类的类
Q3:“带有 Default Constructor”的 Member Class Object
说明:若一个类没有任何构造函数,但它内含一个成员对象,该成员对象有默认构造函数,那个这个类的 implicit default constructor 就是 ” nontrivial”,编译器需要为该类合成出一个默认构造函数
1) Eg: 不满足程序的需求,无自定义构造函数
class A
{
public:
A();
};
class B
{
public:
A a;
char * ptr;
};
int main()
{
B b;
//...
}
• 在上述程序中,将合成一个默认构造函数
• 合成的默认构造函数仅调用A的默认构造函数来处理 B::a
• 并无任何代码处理 B::ptr
• B::a 的初始化时编译器的责任,而 B::ptr的初始化时程序员的责任
2) Eg:不满足程序的需求,且存在自定义构造函数
class A
{
public:
A();
};
class B
{
public:
B(){ ptr = 0; }
A a;
char * ptr;
};
int main()
{
B b;
//...
}
上述类B的构造函数将被扩展为:
B()
{
a.A::A(); //强行插入调用类成员对象的默认构造函数的代码,按声明顺序插入
ptr = 0;
}
• 在上述程序中,将不会合成默认构造函数,因为已经人为定义了
• 如果类中含有类对象成员,则该类的每一个构造函数都必须调用每一个类对象成员的默认构造函数
• 在无法合成的情况下,将在用户代码执行前,强行插入调用必要的默认构造函数的代码
Q4:“带有Default Constructor”的 Base Class
说明,若类继承自带有默认构造函数的基类,则该继承类在编译的时候需要调用基类的默认构造函数。按声明顺序调用。
对于同时存在类对象成员的情况:按声明顺序先调用基类的默认构造函数,再按声明顺序调用类成员对象的默认构造函数
Q5:“带有一个 Virtual Function ”
说明:若有以下两种情况,需要合成默认构造函数:
1) class声明(或继承)一个虚函数
2) class派生子一个继承串链,其中有一个或多个virtual base class
以下扩张行为将发生在编译器:
1) 编译器生成一个 virtual function table,其中放置类的虚函数的地址
2) 每一个类对象中,编译器将合成一个额外的指针成员(vptr),内含与其相关的 类 vtbl 的地址
• 对类定义的每一个构造函数,编译器将安插一些代码来为类对象的 vptr 设置初值,放置何时的 vitual table 的地址
• 对没有定义构造函数的类,将合成默认构造函数,以便正确的初始化每一个类对象的 vptr
Q6:“带有一个Virtual Base Class”
说明:共同点 – 虚基类在派生类中的位置必须在执行期准备妥当
对于以下例子进行分析:
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; };
void foo(A * pa)
{
pa->i = 1024;
}
int main()
{
foo(new A);
foo(new C);
}
分析:编译器将无法固定住 foo()中 “通过 pa 而存取的 X::i 的实际偏移位置”,因为 pa 的真正类型无法确定。编译器必须改变 “执行存取操作的代码”,使得 X::i 可以延迟至执行器才确定下来。
一种办法:在派生类对象的每一个虚基类中安插一个指针,派生类中所有通过引用与指针来存取一个虚基类的操作都可以通过相关指针完成。foo()可改写为:
void foo(A * pa)
{
pa->__vbcX->i = 1024;
}
其中,__vbcX表示编译器生成的指向虚基类 X 的指针。__vbcX 是在类对象的构造期间生成的,对类所定义的每一个构造函数,编译器将要安插那些 “允许每一个虚基类的执行期存取操作的代码”。