《深度探索C++对象模型》第二章:构造函数语义学
类型转换运算符函数:
operator 类型名() { //由类型名来确定返回值类型
实现转换的语句
...
}
while(cin >> val) 原理
cin是一个流对象,而>>运算符返回左边的流对象,也就是说cin >> val返回cin,于是while(cin >> val)就变成了while(cin),问题就变成了一个流对象在判断语句中的合法性。
operator void *() const;函数在while(cin)或是if(cin)时被调用,将流对象转换成void*类型。
bool operator!() const;函数在while(!cin)或是if(!cin)时被调用,将流对象转换成bool类型。
#include<iostream>
using namespace std;
class A
{
public:
A() {}
~A() {}
operator void* () const
{
cout << "cast to void*; ";
return (void *)this;
}
bool operator ! () const
{
cout << "cast to bool; ";
return true;
}
};
int main()
{
A a;
if (a) cout << "first" << endl;
if (!a) cout << "second" << endl;
return 0;
}
//cast to void*; first
//cast to bool; second。
Conversion 运算符实际上很难在一种可预期的良好行为模式下使用。它的引入是明智的,但是测试是严酷的。
由此引出了implicit和explicit关键词
2.1 Default Constructor 的构造操作
default constructors 在需要的时候被编译器产生出来
以下四种情况,会造成“编译器必须为未声明constructors的classes合成一个default constructor”:
- 带有default constructor 的member class object
- 带有default constructor 的base class
- 带有一个virtual function 的class
- 带有一个virtual base class 的class
如果有已经存在的constructor,那么会在用户自己的代码之前安插编译器需要的代码,以完成一些初始化。
c++新手的两个常见的误解:
- 任何class如果没有定义default constructor,就会被合成出来一个
- 编译器合成出来的default constructor 会显示设定class内每一个data member的默认值
2.2 copy constructor 的构造操作
copy constructor 在必要的时候才有编译器产生出来
以下四种情况,一个class不展现出bitwise copy semantics,即编译器会为其合成一个default copy constructor:
- class内含一个member object,而后者的class声明有一个copy constructor(可以是设计者显示声明的,也可以是编译器合成的)
- class继承自一个base class,而后者存在一个copy constructor(可以是显示声明也可是合成的)
- class 声明了一个或多个virtual function(为设定正确的vptr)
- class 派生自一个继承串链,其中有一个或多个virtual base classes为(设定正确的base class的偏移值)
2.3 程序转化语义学
在严谨的c++用词中,“定义”是指“占用内存”的行为
NRV优化:省去了局部变量,减少了一次拷贝构造的开销
有了拷贝构造函数,不能bitwise,较慢,故编译器会进行NRV优化
在使用memcpy()或memset时,要注意class内部是有含有vptr,vbtr
2.4 成员初始化列表(member initialization list)
为了让程序正确编译,一下四种情况,必须使用成员初始化列表:
- 当初始化一个reference member时(c++11在成员声明的时候可以直接设定初值,而不需要再函数中,下同)
- 当初始化一个const member时
- 当调用一个base class 的constructor,而它拥有一组参数时
- 当调用一个member class的constructor,而它拥有一组参数时
编译器会在constructor中以适当的顺序安插初始化列表,且在用户代码之前,list中项目的安插顺序以members的声明顺序决定。
可以调用一个member function 以设定一个member的初值,这种方法是合法的,因为在进入constructor的时候this指针已经明确:
//X::xfool()被调用
X::X(int val) : i(xfoo(val)), j(val) {
}
子类的成员函数被用作父类的构造函数的参数:
class FooBar : public X {
int _fval;
public:
int fval() { return _fval; }
FooBar( int val )
: _fval( val ),
X( fval() ) //先调用父类的构造函数,但是此时val还未设定初值,故结果不是想要的
{}
...
};