目录
2.1 默认构造函数的建构操作
以下四种情况下编译器会合成默认构造函数
(1)带有默认构造函数的成员类对象
如果一个类中没有任何构造函数,但他内含一个成员对象,而后者存在默认构造函数,则编译器会为此类合成一个默认构造函数。不过该合成操作只有在构造函数真正需要被调用时才会发生。
例如下述类
class Foo{public:Foo(),Foo(int)...};
class Bar{public:Foo foo;char *str;}; //内含Foo
Bar合成默认构造函数,该函数要能够处理foo部分,但他不处理str,将Bar::foo初始化是编译器的责任,而将str初始化是程序员的责任
若Bar定义了如下构造函数 Bar::Bar() {str=0;},则这个时候编译器不会合成默认构造函数,而是扩张该构造函数,向该构造函数安插一些代码,在该代码执行前先调用必要的默认构造函数。即:
Bar::Bar()
{
foo.Foo::Foo();
str=0;
}
当包含有多个类对象的时候,会按照类的声明次序进行调用各个构造函数。
class Dopey{ public:Dopey();.......}
class Sneezy{public:Sneezy(int);Sneezy();.....};
class Bashful{public:Bashful();....};
class Snow_White
{
public:
Dopey dopey;
Sneezy sneezy;
Bashful bashful;
private:
int mumble;
}
若Snow_White没有定义默认构造函数,则系统合成一个默认构造函数,若定义了如下构造函数:
Snow_white::Snow_White():sneezy(1024)
{
mumble=2048;
}
则编译器会扩张为:
Snow_white::Snow_White():sneezy(1024)
{
dopey.Dopey::Dopey();
sneezy.Sneezy::Sneezy(1024)
bashful.Bashful::Bashful();
mumble=2048;
}
(2)派生类
如果一个没有任何构造函数的派生类继承自一个带有默认构造函数的基类,那么这个派生类需要一个构造函数,他将调用基类的默认构造函数,对于派生类,这个合成的构造函数和一个被明确的默认构造函数没有区别。
(3)带有虚函数的类
以下两种情况也需要合成
- 类中声明或继承了一个虚函数
- 类派生自一个继承串链,其中有一个或更多的虚基类。
(4)带有一个虚基类的类
(5)总结
上述四种情况编译器会合成一个默认构造函数,该构造函数只是满足编译器的需要,除了上述的情况之外而没有声明任何构造函数的类,我们说他们是拥有非必要的默认构造函数,他们实际上并不是被合成出来的。
在合成的默认构造函数中,只有base class subobjects和member class objects会被初始化,所有其他的非静态成员数据,如整数、征数指针和整数数组都不会被初始化。如果程序需要一个把某指针设为0的默认构造函数,则提供该函数的应该是程序员。
常见误解:
- 任何类没有定义构造函数,就会被合成出来
- 编译器合成处理的默认构造函数会明确设定类内每一个成员的默认值。
2.2复制构造函数的建构操作
以下三种情况会调用复制构造函数:
(1)对一个对象做明确的初始化操作
class X{.......}
X x;
X xx=x;
(2)当ui想被用作参数传递给某个函数:
void foo{X x};
void bar()
{
X xx;
foo(xx);
}
(3)当一个函数传回一个对象时:
X foo_bar()
{
X xx;
return xx;
}
2.2.1 位逐次拷贝
class String
{
public:
//没有复制构造函数
private:
char *str;
int len;
};
class World
{
public:
// 没有复制构造函数
private:
int _occurs;
String _word;
}
假设下面程序中:
Word noun("book")
void foo()
{
Word verb=noun;
}
若该类定义了复制构造函数,则会被调用,若没有定义,编译器是否会合成呢?
假设word是如下定义
class Word
{
public:
Word(const char*);
~Word(){delete [] str}
private:
itn cnt;
char *str;
}
该情况下,不需要合成一个默认复制构造函数,而verb的初始化操作也不需要以一个函数调用收场。
但若word是如下声明
class Word
{
public:
Word(const String &);
~Word();
private;
itn cnt;
String str;
}
上述的String声明了一个复制构造函数,在该情况下,编译器必须合成一个复制构造函数以调用string的复制构造函数
inline Word::word(const Word&wd)
{
str.String::String(wd.str);
cnt=wd.cnt;
}
2.2.2 不进行位逐次拷贝
以下四种情况一个类中不展现出位逐次拷贝:
- 当一个类中内涵一个成员对象,而后者的类生命有一个复制构造函数时。
- 当类继承自一个基类,而后者存在一个复制构造函数时
- 当类声明了一个或多个虚函数时
- 当类派生自一个继承串链,其中有一个或多个虚基类时
前两种情况,编译器必须将成员或基类的复制构造函数调用操作安插到被合成的复制构造函数中。
2.2.3 重新设定虚函数表的指针
在编译时的扩张操作:
- 增加一个虚函数表,内含一个有作用的虚函数地址
- 将一个指向虚函数表的指针(vptr),安插在每一个类对象中
下面将讨论vptr的初始化问题
class ZooAnimal
{
public:
ZooAnimal();
virtual ~ZooAnimal();
virtual void animate();
virtual void draw();
private:
//数据
}
class Bear:public ZooAnimal
{
public:
Bear();
void animate(); //虚函数
void draw(); //虚函数
virtual void dance();
private:
//数据
}
若ZooAnimal以另一个ZooAnimal对象或者Bear以另一个Bear对象作为初值进行初始化,则虚函数的指针会进行如下指向。且将vptr的值进行拷贝是安全的
而当一个基类以其派生类的对象内容进行初始化时,其vptr的复制操作也必须安全
Bear yogi;
ZooAnimal franny=yogi
franny的vptr不可以设定指向Bear 类的虚函数。
即,合成出来的ZooAnimal的复制构造函数会明确设定类的vptr指向ZooAnimal类的虚函数,而不是直接从右手边的类对象中将其vptr的值拷贝过来。
2.3 成员们的初始化队伍
下列情况中,必须使用成员初始化列表,程序才能顺利被编译
- 初始化一个引用成员时
- 初始化一个const成员时
- 调用一个基类的构造函数,而他拥有一组参数
- 调用一个成员类的构造函数,而他拥有一组参数时
上述四种情况,程序可以被正确编译并执行,但效率不高,例
class Word
{
string _name;
int _cnt;
public:
Word(){ _name=0; _cnt=0;}
};
上述类中,Word的构造函数会产生一个暂时性的string对象,然后将他初始化,再以一个复制运算符将暂时性的对象指定给_name,然后再销毁暂时性的对象。其构造函数扩张如下
Word::Word
{
//调用String的默认构造函数
_name.String::String();
//产生暂时性对象
String temp=String(0);
//拷贝
_name.String::operator=(temp);
//销毁暂时性对象
temp.String::~String();
_cnt=0;
}
使用成员列表初始化效率会更高
Word::Word:_name(0)
{
_cnt=0;
}
会被扩张成下述形式
Word::Word()
{
//调用String(int)构造函数
_name。String::String(0);
_cnt=0;
}
成员的初始化序列和类中成员的声明次序决定的,而不是由初始化列表中的排序次序决定的。
初始化次序和成员列表排序次序错误列子
class X
{
int i;
int j;
public:
X(int val):j(val),i(j){}
};
正确写法:
X::X(int val):j(val)
{
i=j;
}
上述中,初始化列表会被放在后面的构造函数之前执行