问题:对于memberwise和bitwise还不是很理解,而这两个概念却非常重要。
开篇一句话:关键词explicit之所以被引入这个语言,就是为了提供给程序员一种方法,使他们能够制止“单一参数的constructor”被当做一个conversion运算符。Conversion运算符引入应该是明智的,测试应该是严酷的(确实如此,一个隐式转换很难被发现),并且在程序一出现不寻常活动第一个症状时发出疑问。
第2.1节 Default Constructor的建构操作
区分Default Constructor
一部分是由编译器在需要时候产生出来;
一部分是程序员为了保证程序正确性,程序员设计Default Constructor。
下面讨论nontrivial default constructor四种情况。
“带有 Default Constructor ”的 Member Class Object
class Foo{public:Foo(),Foo(int)...}; class Bar{public:Foo foo;char* str;}; void foo_bar() { Bar bar; }
Bar::foo初始化时编译器责任,Bar::str初始化则是程序员责任。
因此需要自己编写constructor函数,执行顺序为编译器先调用Foo default constructor,随后执行user code
如果有多个class member object要求以“memberobjects 在class中声明次序”来调用各个constructor
“带有 Default Constructor”的Base Class
貌似没什么说的,基类构造函数要在派生类类成员构造函数之前执行。。。
“带有一个Virtual Function”的Class
讨论放在第5章以后
“带有一个Virtual Base Class”的Class
依然没有看到什么太多值得讨论地方,可能后面会有详细阐述
总结
在合成的default constructor中,只有base class subobjects和member class objects会被初始化,所有其他nonstatic data member都不会被初始化。
C++新手两个常见误解:
1.任何class如果没有定义default constructor,就会被合成一个出来。
2.编译器合成出来的default constructor会明确设定“class内每一个data member默认值”。
第2.2节 Copy Constructor的建构操作
这一节可以解答我前面的疑问,慢慢来解答
Default memberwise initialization定义
把每一个内建的或派生的data member(例如一个指针或者数组)的值,从某个object拷贝一份到另一个object身上。不过它并不拷贝其中member class object,而是以递归方式施行memberwise initialization
Bitwise Copy Semantics(位逐次拷贝)
//以下声明展现出了bitwise copy semantics class Word { public: Word(const char*); ~Word(){delete []str;} //... private: int cnt; char *str; };
注意data member,均没有构造函数,这种情况下不需要合成一个default copy constructor
但是class Word变换一下data member
//以下声明未展现出了bitwise copy semantics class Word { public: Word(const string&); ~Word(){delete []str;} //... private: int cnt; string str; }; //string声明了一个explicit copyconstructor class string { public: string(const char*); string(const string&); ~string(); };
这种情况下编译器必须合成一个copy constructor以便调用member class string object的copy constructor
不要Bitwise Copy Semantics!
1.当class内含一个member object而后者class生命有一个copy constructor时(上例就是这种情况)
2.当class继承自一个base class而后者存在一个copy constructor时
3.当class声明了一个或多个virtual function时
4.当class派生自一个继承串链,其中有一个或多个virtual base classes时
其中3和4情况比较复杂,在后面讨论
重新设定Virtual Table的指针
如果编译器对于每一个新产生的class object的vptr不能成功而正确设定初值,将导致可怕后果。因此,当编译器导入一个vptr到class之中时,该class将不再展现bitwise semantics。现在编译器需要合成出一个copy constructor,以求将vptr适当初始化。
定义两个类,ZooAnimal和Bear:
class ZooAnimal { public: ZooAnimal(); virtual ~ZooAnimal(); virtual void animate(); virtual void draw(); //... private: //ZooAnimal的animate()和draw() //所需要的数据 }; class Bear:public ZooAnimal { public: Bear(); void animate(); void draw(); virtual void dance(); //... private: //Bear的animate()和draw()和dance() //所需要的数据 };
下面例子可以靠“bitwise copy semantics”完成
Bear yogi;
Bear winnie=yogi;
原因是yogi和winnie是同一个class,因此根据bitwise copy semantics把yogi的vptr值拷贝给winnie的vptr是安全的
Bear yogi;
ZooAnimal franny=yogi;
在这种情况下不可以把yogi的vptr赋值给franny的vptr,否则会发生错误
对于虚基类讨论放在第三章
第2.3节 程序转化语意学
明确的初始化操作
1.重写每一个定义,其中初始化操作会被剥夺。
2.class的copy constructor调用操作会被安插进去。
参数初始化
X xx;
foo(xx);
实际上这是一个值传递,并不会改变对象xx,产生C++伪代码如下
//编译器产生出临时对象 X __temp0; //编译器调用copy constructor __temp0.X::X(xx); //改写函数调用操作 foo(__temp0);
foo()声明也因此要改变,像这样:
void foo(X& x0);
返回值初始化
X bar() { X xx; // 处理xx... return xx; }
双阶段转化:
1.首先加上一个额外参数,类型是class object的一个reference。用来放置被“拷贝建构”得到的返回值
2.在return指令前安插一个copy constructor调用操作,以便将欲传回之object的内容当做上述新增参数的初值。
产生C++伪代码如下:
void bar(X& __result) { X xx; xx.X::X(); //...处理xx //编译器产生copy constructor调用操作 __result.X::X(xx); return; }
使用者层面做优化
X bar(const T&y,const T& z) { return X(y,z); }
去除了不必要的copy constructor处理
编译层面做优化
提到一个重要的概念:NRV(Named Return Value)优化
该优化被视为标准C++编译器一个义不容辞的优化操作
X bar() { X xx; //...处理xx return xx; } 编译器把其中xx以__result取代: void bar(X& __result) { __result.X::X(); //...直接处理__result return; }
如果不适用NRV优化策略,则会多调用一次构造和析构函数(X xx)以及拷贝构造函数
同时NRV也带来了一些问题:
1.优化是由编译器默默完成,是否真的完成,并不十分清楚。
2.一旦函数变得比较复杂,优化变得难以施行。比如函数中含有嵌套,cfront就会静静关闭优化。
3.某些程序员真的不喜欢应用程序被优化。
拷贝构造函数要注意,如果有虚函数,不要使用memset方式,因为会修改vptr,导致不正确。
第2.4节 成员们的初始化队伍
出现下述情况,应该使用member initialization list:
1.当初始化一个reference member
2.当初始化一个const member
3.当调用一个base class的constructor,而它拥有一组参数
4.当调用一个member class的constructor,而它拥有一组参数
class Word { string _name; int _cnt; public: //没有错误,但是效率低 Word() { _name=0; _cnt=0; } }; //constructor可能扩张结果 Word::Word() { //调用string的default constructor _name.string::string(); //产生暂时性对象 string temp=string(0); //"memberwise"的拷贝_name _name.string::operator=(temp); //摧毁临时对象 temp.string::~string(); _cnt=0; }
list中的项目次序是由class中的member声明次序决定,而不是initialization list中的排列次序决定。
一个忠告:使用“存在于constructor体内的一个member”,而不要使用“存在于member initialization list中的member”,来为另一个member设定初值。
//调用FooBar::fval()可以吗 class FooBar:public X { int _fval; public: int fval(){return _fval;} function FooBar(int val):_fval(val),X(fval()) }; //可能扩张结果 FooBar::FooBar() { //不是一个好主意 X::X(this,this->fval()); _fval=val; }