class Abstract_base
{
public:
virtual ~Abstract_base() = 0;
virtual void interface()const = 0;
virtual const char* mumble() const{ return _mumble; }
protected:
char* _mumble;
};
上面这个类的设计存在一个问题,虽然这是一个抽象类,但是由于没有提供构造函数,使得派生类的局部对象_mumble无法决定初值(因为基类没有构造函数)。编译器不会提供一个默认的构造函数初始化_mumble的值的,编译器只在需要的时候才会提供默认的构造函数,而且不会将所有的成员变量初始化。
纯虚函数的存在
一个纯虚函数可以被静态调用,但不能被虚拟机制调用,所纯虚函数可以有定义,可以静态调用。特别的,每一个pure virtual destructor,一定要定义。因为每一个Derived class destructor会被编译器加以扩展,以静态调用的方式调用其“每一个virtual base”以及“上一层base class的”destructor,因此,只要缺乏任何一个base class destructors的定义,就会导致链接失败。
虚拟规格的存在
不要把与类型无关的函数设计成虚函数。
虚拟规格中const的存在
一个virtual function是否需要const,似乎是件琐屑的事情,书上建议,在不清楚时,不用const
重新考虑class的声明
destructor 不在是虚函数,提供一个有唯一参数的constructor,纯虚函数不在用const,与类型无关的函数不再是虚函数
class Abstract_base
{
public:
virtual ~Abstract_base();
virtual void interface() = 0;
const char* mumble() const{ return _mumble; }
protected:
Abstract_base(char* pc = 0);
char* _mumble;
};
5.1“无继承”情况下的对象构造
typedef struct
{
float x, y, z;
}Point;
Point global;
Point foobar()
{
Point local;
Point *heap = new Point;
*heap = local;
delete heap;
return local;
}
上述有三种不同的对象产生方式:global内存配置,local内存配置,heap内存配置。对于上述Point的声明,对于
Point global;
观念上的trival constructor和destructor都会被产生并被调用,constructor在程序起始处被调用,destructor在程序exit()处被调用(exit()系由系统产生,放在main函数结束之前)。然而事实上那些trivial members要不是没有被定义。
Point local:
同样既没有被构造也没有被解构。
Point* heap=new Point;
会转换为对new运算符的调用
Point* heap=_new(sizeof(Point));
*heap=local;
观念上会触发trival copy assignment operator进行拷贝搬运操作,实际上就是简单的位拷贝(浅复制)
delete heap;
会被转换为对delete运算符的调用
_delete(heap);
观念上会触发Point trival destructor。但事实destructor要不是没有被产生就是没有被调用。
最后的return,也是简单的位拷贝
抽象数据类型
改变Point的声明
class Point
{
public:
Point(float x=0.0,float y=0.0,float z=0.0)
:_x(x), _y(y), _z(z){}
private:
float _x, _y,_z;
};
为Point定义了构造函数,单数没有copy constructor和copy operator,因为默认的位语意(浅复制)已经足够。也没有提供constructor,因为程序默认的内存管理方法也足够。
Point global;
有默认的default constructor作用其上
如果对class中的所有成员都设定常量初值,那么给予一个explicit initialization list会比较高效。举例
void mumble()
{
Point local1={1.0,1.0,1.0};
Point local2;
local2.x=1.0;
local2.y=1.0;
local2.z=1.0;
}
local1的初始化操作会比local2的高效。这是因为当函数activation record被放进程序堆栈时。上述initialization list中的常量就可以被放进local1内存中了。
explicit initialization list带来三项缺点
1.只有当class member都是public时,此法才奏效
2.只能指定常量,因为它们在编译时期就可以被评估求值
3.由于编译器并没有自动施行之,所以初始化行为的失败可能性会比较高一些
在编译器层面,会有一个优化机制用来识别inline constructors,后者简单地提供一个member-by-member的常量指定操作。然后编译器会抽取那些值,并且对待它们就好像是explicit initialization list 所供应的一样,而不会把constructor 扩展称为一系列的assignment指令
于是对于local Pointo bject的定义,被附加default Point constructor的inline expansion
Point local;
//变为
Point local;
local._x=0.0;local._y=0.0;local._z=0.0;
对于heap的定义。被附加一个“对default Point constructor”的有条件调用操作,然后又被编译器进行inline expansion操作
Point* heap=_new(sizeof(point));
if(heap!=0)
{
heap->Point::Point();
}
对于
*heap=local;
return local;
均是简单的位拷贝操作
delete heap;
没有调用destructor,因为没有明确提供destructor
观念上我们的Point class有一个相关的default copy constructor、copy operator 和destructor,然而他们都是trival,而且编译器实际上根本没有产生他们。
为继承做准备
class Point
{
public:
Point(float x=0.0,float y=0.0)
:_x(x), _y(y){}
//no destructor ,copy constructor,orcopy operator defined
virtual float z();
private:
float _x, _y;
};
为Point添加一个虚函数,每一个class object会负担一个vptr,而且会使得Point class产生膨胀
1.我们所定义的constructor被附加了一些码,以便将vptr初始化,这些码必须被附加在人和base class constructor的调用之后,但必须在任何使用者(程序员)供应的码之前
2.合成一个copy constructor和一个copy assignment operator,而且其操作不再是trival(但implicit destructor仍然是trival)
Point global;
Point foobar()
{
Point local;
Point *heap = new Point;
*heap = local;
delete heap;
return local;
}
global,local,heap 的初始化操作以及heap的删除操作,还是和之前的Point版本相同
但是*heap=local;
可能会触发copy assignment operator的合成,及其调用操作的一个inline expansion(内联操作):以this取代heap,以rhs取代local;
return local;理论上回调用copy constructor,会触发NVR优化,就不会在调用copy constructor了。
5.2继承体系下的对象构造
编译器会示class的继承体系扩充class的operator;
1.记录在member initialization list中的data members初始化操作会被放进constructor的函数本身,并以member的声明顺序为顺序
2.如果有一个member并没有出现在member initialization list之中,但它有一个default constructor,那么该default constructor必须被调用
3.在那之前,如果class object有virtual table pointer(s),它们必须被设定初值,指向适当的virtual table(s)
4.在那之前,所有上一层的base class constructors必须被调用,以base class的声明顺序为顺序,于member initialization list中的顺序无关
a.如果base class被列于member initialization list中,那么任何明确指定的参数都应该传递过去
b.如果base class没有被列于member initialization list中,而它有default constructor,那么调用之
c.如果base class是多重继承下的第二或后继的base class,那么this指针必须有所调整
5.在那之前,所有virtual base class constructor必须被调用,从左到右,从最深到最浅
a.如果base class被列于member initialization list中,那么任何明确指定的参数都应该传递过去,如果base class没有被列于member initialization list中,而它有default constructor,那么调用之
b.此外,class 中的每一个virtual base subobject的偏移量必须在执行期可被存取
c.如果class object是最底层(most-derived)的class,其constructors可能被调用,某些用以支持这个行为的机制必须被放进来。(最后一层的派生类执行virtual base的初始化操作)
vptr初始化语意学
vptr的初始化,在base class constructors调用操作之后,但是在程序员供应的码或是“member initialization list中所列的members初始化操作”之前
5.3对象复制语意学
在设计类时,把一个class object指定给另一个class object时。我们有三种选择
1.什么都不做,因此得以实施默认行为
2.提供一个explicit copy assignment operator
3.明确地拒绝吧一个class object指定给另一个 class object。(通过将copy assignment operator声明为private,并且不提供定义)
对于第一种情况,如果我们只需要支持简单的拷贝,则第一种情况就足够,不需要额外提供copy assignment operator。编译器一般不会产生copy assignment operator,但下列情况例外
1.当class内带一个member object,而其class有一个copy assignment operator时
2.当一个class的base class有一个copy assignment operator时
3.当一个class声明了任何virtual functions
4.当class继承自一个virtual base class
copy assignment operator可能会导致virtual base class的copy assignment operator被多次调用(对于constructor解决方案是有一个bool类型的_most_derived参数用以判断是否是最后一个派生类,在最后一个派生类中调用其constructor),所以尽可能不要允许一个virtual base class的拷贝操作,或者不要再任何virtual base class中声明数据。
5.5解构语意学
如果class没有定义的destructor,那么只有在class内带的member object(或是class自己的base class)拥有destructor的情况下,编译器才会自动合成出一个来,否则不合成。
destructor的调用顺序与constructor的顺序是相反的