第2章 构造函数语意学 (The Semantics of Constructor) 关于C++,最常听到的一个抱怨就是,编译器背着程序员做了太多事情.Conversion运算符就是最常被引用的一个例子.
2.1 Default Constructor的建构操作 C++ Annotated Reference Manual
(ARM)指出default constructors ...在需要的时候被编译器产生出来.关键字眼是在需要的时候.被谁需要?做什么事情?看看下面这段程序代码:
class Foo {
public:
int val;
Foo *pnext;
};
void foo_bar() {
Foo bar;
if (bar.val || bar.pnext)
// ... do something
} 在这个例子中,正确的程序语意是要求Foo有一个default constructor,可以将它的两个members初始化为0,上面这段代码不符合ARM所述的在需要的时候.其间的差别在于一个是程序的需要,一个是编译器的需要.上述代码不会合成出一个default constructor.
那么
什么时候才会合成出一个default constructor呢?当编译器需要它的时候!此外,被合成出来的constructor只执行编译器所需的行动,也就是说,即使有需要为class Foo合成一个default constructor,那个constructor也不会将两个data members val和pnext初始化为0.为了让上一段代码正确执行,class Foo的设计者必须提供一个显式的default constructor,将两个members适当地初始化.
C++ Standard已经修改了ARM的说法,虽然其行为事实上仍然相同.C++ Standard指出对于class X,如果没有任何user-declared constructor,那么会有一个default constructor被隐式声明出来....一个被隐式声明的default constructor将是一个trivial constructor...
带有 Default constructor的Member Class Object 如果一个class没有任何constructor,但它内含一个member object,而后者有default constructor,那么这个class的implicit default constructor就是nontrivial,编译器需要为此class合成出一个default constructor,不过这个合成操作只有在constructor真正需要被调用时才会发生.
于是出现一个有趣的问题:
在C++各个不同的编译模块中,编译器如何避免合成出多个default constructor(譬如说一个是为A.C档合成,另一个为B.C档合成)?解决的办法是把合成的default constructor,copy constructor,destructor,assignment copy operator都以inline方式完成.一个inline函数有静态链接(static linkage),不会被档案以外者看到.如果函数太复杂,不适合做成inline,就会合成出一个explicit non-inline static实体.
例如,下面的程序片段中,编译器为class Bar合成一个default constructor:
class Foo {
public:
Foo();
Foo(int)
...
};
class Bar {
public:
Foo foo;
char *str;
};
void foo_bar() {
Bar bar; //Bar::foo必须在此处初始化
//Bar::foo是一个member object.而其class Foo拥有defautl constructor
if (str) {
...
}
}; 被合成的Bar default constructor内含必要的代码,能够调用class Foo的default constructor来处理member object Bar::foo,但它并不产生任何代码来初始化Bar::str.将Bar::foo初始化是编译器的责任,将Bar::str初始化是程序员的责任,被合成的default constructor可能如下:
// Bar的default constructor可能被这样合成
// 被member foo调用class Foo的default constructor
inline Bar::Bar() {
// C++伪代码
foo.Foo::Foo();
}
? 注意,被合成的default constructor只满足编译器的需要,而不是程序的需要,为了让这个程序片段能够正确执行,字符指针str也需要被初始化.假设程序员经由下面的default constructor提供了str的初始化操作:
// 程序员定义的default constructor
Bar::Bar() { str = 0; } 现在程序的需求获得满足了,但是编译器还需要初始化member object foo.由于default constructor已经被显式定义,编译器没有办法合成第二个.
编译器采取行动:如果 class A内含一个或一个以上的member class objects,那么 class A的每一个constructor必须调用每一个member classes的default constructor,编译器会扩张已存在的constructors,在其中安插一些码,使得user code在被执行之前,先调用必要的default constructors.沿续前一个例子,扩张后的constructors可能像这样:
// 扩张后的default constructor
// C++伪代码
Bar::Bar() {
foo.Foo::Foo(); // 附加上complier code
str = 0; // 显式user code
}
如果有多个class member objects都要求constructor初始化操作,将如何呢?
C++语言要求以member objects在class中的声明次序来调用各个constructors.这一点由编译器完成,它为每一个constructor安插程序代码,以member声明次序调用每一个member所关联的default constructor.这些码被安插在显式user code之前.如果有如下所示三个classes:
class Dopey {
public:
Dopey();
};
class Sneezy {
public:
Sneezy(int);
Sneezy();
};
class Bashful {
public:
Bashful();
};
以及一个 class Snow_White:
class Snow_White {
public:
Dopey dopey;
Sneezy sneezy;
Bashful bashful;
private:
int numb |