第5章 构造、析构、拷贝语义学2: 继承情况下的对象构造 ...

打印 上一主题 下一主题

主题 926|帖子 926|积分 2778

重点:构造函数调用次序
当我们界说一个object如:T object;如果T有一个constructor(不管是trival还是nontrival),它都会被调用。

  • 记录在member initialization list中的data members初始化操作放在constructor的函数体内,并且以members的声明次序为次序
  • 如果一个member没有出现在members initialization list中,但是它有一个默认构造函数,那么这个默认构造函数必须被调用
  • 在那之前,如果class object有virtual table pointer(s),它们必须被设定初始值,指向恰当的virtual table
  • 在那之前,全部上一层的base class constructor必须被调用,以base class 的声明次序为次序(与members initialization list中的次序没关联)

    • 如果base class被列于members initialization list中,那么任何表现指定的参数都应该通报已往
    • 如果base class没有被列于member initialization list中,而他有default constructor(或default memberwise copy constructor),那么就调用之
    • 如果base class是多重继承下的第二大概后继的base class,那么this指针应该有所调整

  • 在那之前,全部virtual base class constructor必须被调用,从左往右,从最深到最浅

    • 如果class位member initialization list,那么有任何表现指定的参数都应该通报已往;若没有位于list,而class含有一个默认构造函数,也应该调用。
    • class中的每一个virtual base class subobject的偏移量(offset)必须在执行期可存取
    • 如果class object是最底层(most-derived)的class,其constructors可能被调用;某些用以支持这一活动的机制必须被放进来

  1. //以point为例
  2. class Point {
  3. public:
  4.     Point(float x = 0.0, float y = 0.0);
  5.     Point(const Point&);
  6.     Point& operator=(const Point&);
  7.     virtual ~Point();
  8.     virtual float z() { return 0.0; }
  9. private:
  10.     float _x, _y;
  11. };
  12. //以Point为基础的类Lines的扩张过程
  13. class Lines {
  14.     Point _begin, _end;
  15. public:
  16.     Lines(float = 0.0, float = 0.0, float = 0.0, float = 0.0);
  17.     Lines(const Point&, const Point&);
  18.     draw();
  19. };
  20. //来看第二个构造函数的定义与内部转化
  21. Lines::Lines(const Point& begin, const Point& end) :
  22.     _begin(begin), _end(end){} //定义
  23. //下面是内部转化,将初值列中的成员构造放入函数体,调用这些成员的构造函数
  24. Lines* Lines::Lines(Lines *this, const Point& begin, const Point& end) {
  25.     this->_begin.Point::Point(begin);
  26.     this->_end.Point::Point(end);
  27.     return this;
  28. }
  29. //我们写下
  30. Lines a;
  31. //合成的destructor在内部可能如下,
  32. //如果line派生于point那么合成的destructor将会是virtual
  33. //然而由于line是内含point object而不是继承,
  34. //   那么合成出来的destructor是nonvirtual;
  35. //   如果point destructor是inline,那么每一个调用点会被扩张
  36. inline void
  37. Lines::~Lines(Lines *this) //
  38. {
  39.     this->_begin.Point::~Point();
  40.     this->_end.Point::~Point();
  41. }
  42. Lines b = a;
  43. //这个时候调用合成的拷贝构造函数,合成的拷贝构造在内部可能如下
  44. inline Lines&
  45. Lines::Lines(const Lines& rhs) {
  46.     if(*this = rsh) return *this;   
  47.     //证同测试,或者可以采用copy and swap,具体见effective C++
  48.     //还要注意深拷贝和浅拷贝
  49.     this->_begin.Point::Point(rhs._begin);
  50.     this->_end.Point::Point(rhs._end);
  51.     return *this;
  52. }
复制代码
应该在copy operator中检查自我指派是否失败。
在copy operator中面临自我拷贝计划一个自我过滤操作
一、虚拟继承

重点:虚拟继承下constructor怎样调用

  1. class Point3d : public virtual Point {
  2. public:
  3.     Point3d(float x = 0.0, float y = 0.0, float z = 0.0)
  4.         : Point(x, y), _z(z) { }
  5.     Point3d(const Point3d& rhs)
  6.         : Point(rhs), _z(rhs._z) { }
  7.     ~Point3d();
  8.     Point3d& operator=(const Point3d&);
  9.     virtual float z() { return _z; }
  10. private:
  11.     float _z;
  12. }
复制代码
 传统的constructor扩张并没有用,因为virtual base class的共享性的缘故原由。试想以下继承关系
  1. class Vertex : virtual public Point { };
  2. class Vertex3d : public Point3d, public Vertex { };
  3. class PVertex : public Vertex3d { };
复制代码
 

当point3d和vertex同为vertex3d的subobject时,对point constructor的调用操作一定不可以发生,作为一个最底层的class pvertex,有责任将point初始化
constructor的函数本体因而必须条件式地侧测试传进来的参数,然后决定调用或不调用相关的virtual base class constructor。
Point3d的constructor的扩充如下
  1. //c++伪码
  2. Point3d*
  3. Point3d::Point3d(Point3d* this, bool __most_derived,
  4.                 float x = 0.0, float y = 0.0, float z = 0.0) {
  5.     if(__most_derived != false) this->Point::Point();
  6.     //虚拟继承下两个虚表的设定
  7.     this->__vptr_Point3d = __vtbl_Point3d;
  8.     this->__vptr_Point3d__Point = __vtbl_Point3d__Point;
  9.     this->z = rhs.z;
  10.     return this;
  11. }
复制代码
 而并非如下传统的扩张
  1. //c++伪码
  2. Point3d*
  3. Point3d::Point3d(Point3d* this,
  4.                 float x = 0.0, float y = 0.0, float z = 0.0) {
  5.     this->Point::Point();
  6.     this->__vptr_Point3d = __vtbl_Point3d;
  7.     this->__vptr_Point3d__Point = __vtbl_Point3d__Point;
  8.     this->z = rhs.z;
  9.     return this;
  10. }
复制代码
两种扩张的不同之处在于参数__most_derived,在更加深条理的继承情况下,比方Vextex3d,调用Point3d和Vertex的constructor时,总会将该参数设置为false,于是就克制了两个constructors对Point constructor的调用操作。 比方:
  1. //c++伪码
  2. Vextex3d*
  3. Vextex3d::Vextex3d(Vextex3d* this, bool __most_derived,
  4.                 float x = 0.0, float y = 0.0, float z = 0.0) {
  5.     if(__most_derived != false) this->Point::Point();
  6.     //设定__most_derived为false
  7.     this->Point3d::Point3d(false, x, y, z);
  8.     this->Vertex::Vertex(false, x, y);
  9.     //设定vptrs
  10.     return this;
  11. }
复制代码
当我们界说
  1. Vertex3d cv;
复制代码
 Vertext3d 的constructor 正确的调用 Pointer constructor。
virtual base class constructor被调用有着明白的界说,只有当一个完整的class object被界说出来(比方 Point3d origin)时,它才会被调用,如果object只是某个完整的object的subobject,他就不会被调用
还可以把constructor一分为2,一个针对一个完整的object,一个针对subobject,完整的object无条件调用virtual base constructor,设定vptrs,subobject不调用virtual base constructor,也可能不设定vptrs
二、Vptr初始化语义学

重点:Vptr在那一步做的

界说一个PVertex object时,constructor的调用次序是
  1. Point(x, y);
  2. Point3d(x, y, z);
  3. Vertex(x, y, z);
  4. Vertex3d(x, y, z);
  5. PVertex(x, y, z);
复制代码
假设每个class都界说了一个virtual function size();返回该class的大小。我们来看看界说的PVertex constructor
  1. PVertex::Pvertex(float x, float y, float z)
  2.     : _next(0), Vertex3d(x, y, z), Point(x, y) {
  3.     if(spyOn)
  4.         cerr << "Within Pvertex::PVertex()"
  5.              << "size: " << size() << endl;
  6. }
复制代码
编译器怎样包管恰当的size()被正确的调用? 如果调用操作必须在 constructor 或 destructor 中, 那么答案非常显着:将每一个调用操作以静态决议,千万不要用虚拟机制
vptr的初始化操作:在base class constructor调用操作之后,但是在步伐员供应的代码或”member initialization list中全部列的members初始化操作”之前。如果每一个constructor 都不停等候其 base class construtors 执行完毕之后才设定其对象的vptr,那么每次都可以或许调用正确的 virtual function 实例。
constructor的执行算法通常如下:

  • 在derived class constructor中,全部的virtual base class以及上一层的base class的constructor会被调用。
  • 上述完成之后,对象的vptrs被初始化,指向相关的虚表。
  • 如果有member initialization list的话,将在constructor的函数体内展开,这必须是在vptr设定之后才做的,以免有一个virtual member function被调用。
  • 最后执行步伐员的代码。
之前的PVertex constructor可能被扩张成
  1. PVertex*
  2. PVertex::Pvertex(PVertex*this, bool __most_derived,
  3.             float x, float y, float z) {
  4.     //有条件调用virtual base class constructor
  5.     if(__most_derived != false) this->Point::Point();
  6.     //无条件调用上一层base class constructor
  7.     this->Vertex3d::Vertex3d(x, y, z);
  8.     //设定vptr
  9.     this->__vptr_PVertex = __vtbl_PVertex;
  10.     this->__vptr_Point3d__PVertex = __vtbl_Point3d__PVertex;
  11.     //执行程序员的代码
  12.     if(spyOn)
  13.         cerr << "Within Pvertex::PVertex()"
  14.              << "size: "
  15.              //虚拟机制调用
  16.              << (*this->__vptr_PVertex[3].faddr)(this)
  17.              << endl;
  18.     return this;
  19. }
复制代码
下面是vptr必须被设定的两种情况:

  • 当一个完整的对象被析构起来时,如果我们声明一个Point对象,则Point constructor必须设定其vptr。
  • 当一个subobject constructor调用了一个virtual function时(不论是直接调用还是间接调用)。
在class的 constructor 的 member initialization list中调用该class的一个虚函数,这是安全的。因为vptr的设定总包管在member initialization list扩展之前就被设定好;但是这在语义上时不安全的,因为函数本身可能依赖未被设定初值的成员。
当需要为base class constructor提供参数时,在class的constructor的成员初值列中调用该class的一个虚函数这就是不安全的,此时vptr要么尚未被设定好,要么指向错误的class。该函数存取任何class's data members一定还没有初始化。(子类初始化列表->初始化父类,子类初始化列表含有虚函数的情况)

参考 构造、析构、拷贝语意学 - tianzeng - 博客园

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

八卦阵

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表