纯虚函数的存在
class A { public: A() {} //virtual A() = 0;构造函数不能声明为虚函数,更不能声明为纯虚函数 virtual void interface() = 0; void fun(); private: int a; }; void A::interface() { cout << "我在声明的时候是个纯虚函数" << endl; } void A::fun() { cout << "我是fun函数" << endl; } class B :public A { private: int b; public: B() {} void interface(); }; void B::interface() { A::interface(); A::fun(); } int main() { //A a;错误,凡是包含纯虚函数的类都是抽象类,而抽象类是不能实例化出对象的 B b; b.interface(); system("pause"); return 0; }
class C { C() {} virtual ~C() = 0;//能正常编译通过 };
5.1 “无继承”情况下的对象构造
Point global; Point foobar() { Point local; Point *heap = new point; *heap = local; delete heap; return local; }L1,L5,L6表现出三种不同的对象产生方式:global内存配置、local内存配置和heap内存配置。L7把一个class object指定给另一个,L10设定返回值,L9显式地以delete运算符删除heap object。
下面是Point的第一次声明形式:
typedef struct { float x, y, z; }Point;当定义:
Point global;
观念上Point的trival constructor和destructor都会被产生出来并被调用,constructor在程序起始处被调用而destructor在程序的exit()处被调用(exit()是由系统产生的,放在main()结束之前)。然而,事实上那些trival members要不是没被定义,就是没被调用。
注意:global在C++中被视为完全定义(它会阻止第二个或更多个定义)。C和C++的一个差异就在于:C++的所有全局对象都被以“初始化过的数据”来对待。
比如:
int Global; int main() { cout << Global << endl; system("pause"); return 0; }
foobar()函数中的L5,有一个Point object local,同样也是既没有被构造也没有被析构。当然,Point object loacl如果没有先经过初始化,可能会成为一个潜在的程序“臭虫”,这种情况就是第一次使用它就需要其初值。像L7
至于heap在L6的初始化操作:
Point *heap=new Point;
会被转换为对new运算符(由library提供)的调用:
Point *heap=__new(sizeof(Point));
并没有default constructor施行于new运算符所传回的Point object身上。
L7对此object有个赋值操作,如果local曾被适当地初始化过,就没有问题了
*heap=local;
如果没有初始化,则会报错
这样的指定操作只是像C那样纯粹的位搬移操作,不会触发trivial copy assignment operator。
L9执行一个delete操作:
delete heap;
会被转换位对delete运算符(由library)的调用:
__delete(heap);
此时destructor要不就是没被产生要么就是没有被调用。
最后,函数以传值的方式将local当作返回值传回,这在观念上会触发trivial copy constructor,不过实际上return操作只是一个简单的位拷贝操作,因为对象是个Plain OI Data。
抽象数据类型
class Point { pubblic: 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 class,其大小并没有改变,还是三个连续的float。不论private或public存取层,或是member function的声明,都不会占用额外的对象空间。
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的有效率
//inline expansion of default constructor Point local; local._x = 0.0; local._y = 0.0; local._z = 0.0;
Point *heap = __new(sizeof(Point)); if (heap != 0) heap->Point::Point();然后才又被编译器进行inline expansion操作。
则保持着简单的位拷贝操作。以传值方式传回local object,情况也是一样的:
returnlocal;
为继承做准备
class Point { public: Point(float x = 0.0, float y = 0.0) : _x(x), _y(y){} virtual float z(); protected: float _x, _y; };除了每一个class object多负担一个vptr之外,virtual function的导入也引发编译器对于我们的Point class产生膨胀作用:
2.合成一个copy constructor和一个copy assignment operator(原因是一个class声明了任何virtual functions,就会导致这两个函数的合成,而且是nontrivial), 而且其操作不再是trivial(但implicit destructor仍然是trivial)。如果一个Point object被初始化或以一个derived class object赋值,那么以位为基础的操作可能对vptr带来非法设定。
Point foobar(Point &_result) { Point local; local.Point::Point(0.0, 0.0); //heap的部分与前面的相同 ... //copy constructor的应用 __result.Point::Point(local); //local对象的destructor将在这里执行 //调用Point定义的destructor: local.Point::~Point(); return; }如果支持NRV优化,这个函数会被进一步转化如下:
Point foobar(Point &_result) { __result.Point::Point(0.0, 0.0); return; }
5.2 继承体系下的对象构造
class Point { Point(float x = 0.0, float y = 0.0); Point(const Point&); Point& operator=(const Point&); virtual ~Point(); virtual float z() { return 0.0; } protected: float _x, _y; }; class Line { Point _begin, _end; public: Line(float = 0.0, float = 0.0, float = 0.0, float = 0.0); Line(const Point&, const Point&); void draw(); };在class Line中,每一个exolicit constructor都会被扩充以调用其两个member class objects的constructors。
Line::Line(const Point &begin, const Point &end) :_end(end), _begin(begin) {}会被编译器扩充并转换为;
Line* Line::Line(Line *this, const Point &bebgin, const Point &end) { this->_begin.Point::Point(begin); this->_end.Point::Point(end); return this; }由于Point声明了一个copy constructor、一个copy operator,以及一个destructor,所以Line class的implicit copy constructor、copy operator和destructor都将是nontrivial。
inline void Line::Line(Line *this) { this->_end.~Point::Point(); this->_begin.~Point::Point(); }请注意,索然Point destructor是virtual,但其调用操作会被静态地决议出来(应该是没有继承关系的缘故)。
虚拟继承
class Point3d :public virtual Point { public: Point3d(float x = 0.0, float y = 0.0, float z = 0.0) :Point(x, y), _z(z) {} Point3d(const Point3d& rhs) :Point(rhs), _z(rhs._z) {} ~Point3d(); Point3d& operator=(const Point3d&); virtual float z() { return _z; } protected: float _z; };传统的“constructor扩充现象”并没有用,否则会导致二义性,而虚拟继承正是用于解决二义性。
Point3d* Point3d::Point3d(Point3d *this, float x, float y, float z) { this->Point::Point(x, y); this->__vptr_Point3d = __vtbl_Point3d; this->__vptr_Point3d__Point = __vtbl_Point3d__Point; this->_z = rhs._z; return this; }上面的Point3d constructor扩充内容是错误的。比如在下面的派生情况中:
class Vertex :virtual public Point {}; class Vertex3d :public Point3d, public Vertex {}; class PVertex :public Vertex3d {};
在上述派生关系中,按理说,Vertex的constructor必须也调用Point的constructor。 然而,当Point3d和Vertex同为Vertex3d的subobjects时,它们对Point constructor的调用操作一定不可以同时发生,因为这样就会导致二义性。所以Vertex3d有责任将Point初始化。而更往后的继承,则由PVertex(不再是Vertex3d)来负责完成“被共享之Point subobject”的构造。
+
Point3d* Point3d::Point3d(Point3d *this, bool __most_derived, float x, float y, float z) { if (__most_derived != false) this->Point::Point(x, y); this->__vptr_Point3d = __vtbl_Point3d; this->__vptr_Point3d__Point = __vtbl_Point3d__Point; this->_z = rhs._z; return this; }在更深的继承情况下,例如Vertex3d,调用Point3d和Vertex的constructor时,总是会把__most_derived参数设为false, 于是就压制了两个constructors中对Point constructor的调用操作。
Vertex3d* Point3d::Vertex3d(Vertex3d *this, float x, float y, float z) { if (__most_derived != false) this->Point::point(x, y); //调用上一层base classes this->Point3d::Point3d(fal se, x, y, z); this->Vertex::Vertex(false, x, y); //...设定vptrs的操作 //...安插user code }这样的策略得以保持语意的正确无误。
vptr初始化语意学
Point3d::Point3d(float x, float y, float z) :_x(x), _y(y), _z(z) { if (spyOn) cerr << "Within Point3d::Point3d()" << "size:" << size() << endl; }此时问题来了,当定义PVertex object时,前述的5个constructors会如何?每一次size()调用会被决议为PVertex::size()吗还是目前正在执行的constructor所对应的class的size()函数实例?
PVertex:PVertex(float x, float y, float z) : _next(0), Vertex3d(x, y, z), Point(x, y)//前面提到过,由PVertex负责完成“被共享之Point subobject”的构造 { if (spyOn) cerr << "Within PVertex::PVertex()" << "size:" << size() << endl; }它可能被扩展为:
PVertex* PVertex::PVertex(PVertex* this, bbool __most__derived, float x, float y, float z) { //条件式地调用virtual base constructor if (__most__derived != false) this->Point::Point(x, y); //无条件地调用上一层base this->Vertex3d::Vertex3d(x, y, z); //将相关的vptr初始化 this->__vptr_PVertex = __vtbl_PVertex; this->_vptr_Point_PVertex = __vtbl_Point__PVertex; //程序员所写的代码 if (spyOn) cerr << "Within PVertex::PVertex()" << "szie:" //经由虚拟机制调用 //这就是巧妙之处,this指针是PVertex型的,在构造函数中,size() //肯定就是PVertex::size(),因为PVertex派生类的实例还没构造起来 //(因为这是在构造函数之中)。但若不是在构造函数中调用size(), //这时PVertex派生类的实例已经构造起来,size()就不知道是PVertex的 //还是PVertex派生类的 << (*this->__vptr__PVertex[3].faddr)(this) << endl; //传回被构造的对象 return this; }
5.3 对象复制语意学
inline Point& Point::operator=(const Point &p) { _x = p._x; _y = p._y; return *this; }现在派生一个Point3d class(虚拟继承)
class Point3d :virtual public Point { public: Point3d(float x = 0.0, float y = 0.0, float z = 0.0); protected: float _z; };如果没有为Point3d定义一个copy assignment operator,编译器就必须合成一个,如下所示:
inline Point3d& Point3d::operator=(Point3d* const this, const Point3d &p) { //调用base class的函数实例 this->Point::operator=(p); _z = p._z; return *this; }
5.5 析构语意学
如果class没有定义destructor,那么只有在class内含的member obbject(或者是class自己的base class)拥有destructor的情况下,编译器才会合成出一个来。否则,destructor被视为不需要,也就不需要被合成(当然更不需要被调用)。例如,类Point,默认情况下并没有被编译器合成出一个destructor,甚至虽然在Point中有一个虚函数class Point { public: Point(float x = 0.0, float y = 0.0); Point(const Point&); virtual float z(); private: float _x, _y; };类似的道理,如果把两个Point对象组合成一个Line class:
class Line { public:Line(const Point&, const Point&); virtual draw(); protected: Point _begin, _end; };Line也不会拥有一个被合成出来的destructor,因为Point并没有destructor。
{ Point pt; Point *p = new Point3d; foo(&pt, p); delete p; }我们看到,pt和p在作为foo()函数之前,都必须先初始化为某些坐标值。这时候需要一个constructor,否则使用者必须显式地提供坐标值。一般而言,class的使用者没有办法检验一个local变量或heap变量以知道它们是否被初始化。把constructor想象为程序的一个额外负担是错误的,因为它们的工作有其必要性。
//class Vertex:virtual public Point inline Vertex& Vertex::operator=(const Vertex &v) { this->Point::Operator = (v); //Vertex通过_next维护着一个链表 _next = v._next; return *this; }当我们从Point3d和Vertex派生出Vertex3d时,如果我们不供应一个explicit Vertex3d destructor,那么我们还是希望Vertex destructor被调用,以结束一个Vertex3d object。因此编译器必须合成一个Vertex3d destructor,其唯一任务就是调用Vertex destructor。如果我们提供一个Vertex3d destructor,编译器会扩展它,使它调用Vertex destructor(在我们所供应的程序代码之后)。一个由程序员定义的destructor被扩展的方式类似constructors被扩展的方式,但顺序相反: