重点:构造函数调用次序
当我们界说一个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可能被调用;某些用以支持这一活动的机制必须被放进来
- //以point为例
- class Point {
- public:
- Point(float x = 0.0, float y = 0.0);
- Point(const Point&);
- Point& operator=(const Point&);
- virtual ~Point();
- virtual float z() { return 0.0; }
- private:
- float _x, _y;
- };
- //以Point为基础的类Lines的扩张过程
- class Lines {
- Point _begin, _end;
- public:
- Lines(float = 0.0, float = 0.0, float = 0.0, float = 0.0);
- Lines(const Point&, const Point&);
- draw();
- };
- //来看第二个构造函数的定义与内部转化
- Lines::Lines(const Point& begin, const Point& end) :
- _begin(begin), _end(end){} //定义
- //下面是内部转化,将初值列中的成员构造放入函数体,调用这些成员的构造函数
- Lines* Lines::Lines(Lines *this, const Point& begin, const Point& end) {
- this->_begin.Point::Point(begin);
- this->_end.Point::Point(end);
- return this;
- }
- //我们写下
- Lines a;
- //合成的destructor在内部可能如下,
- //如果line派生于point那么合成的destructor将会是virtual
- //然而由于line是内含point object而不是继承,
- // 那么合成出来的destructor是nonvirtual;
- // 如果point destructor是inline,那么每一个调用点会被扩张
- inline void
- Lines::~Lines(Lines *this) //
- {
- this->_begin.Point::~Point();
- this->_end.Point::~Point();
- }
- Lines b = a;
- //这个时候调用合成的拷贝构造函数,合成的拷贝构造在内部可能如下
- inline Lines&
- Lines::Lines(const Lines& rhs) {
- if(*this = rsh) return *this;
- //证同测试,或者可以采用copy and swap,具体见effective C++
- //还要注意深拷贝和浅拷贝
- this->_begin.Point::Point(rhs._begin);
- this->_end.Point::Point(rhs._end);
- return *this;
- }
复制代码 应该在copy operator中检查自我指派是否失败。
在copy operator中面临自我拷贝计划一个自我过滤操作
一、虚拟继承
重点:虚拟继承下constructor怎样调用
- 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; }
- private:
- float _z;
- }
复制代码 传统的constructor扩张并没有用,因为virtual base class的共享性的缘故原由。试想以下继承关系
- class Vertex : virtual public Point { };
- class Vertex3d : public Point3d, public Vertex { };
- class PVertex : public Vertex3d { };
复制代码
当point3d和vertex同为vertex3d的subobject时,对point constructor的调用操作一定不可以发生,作为一个最底层的class pvertex,有责任将point初始化
constructor的函数本体因而必须条件式地侧测试传进来的参数,然后决定调用或不调用相关的virtual base class constructor。
Point3d的constructor的扩充如下
- //c++伪码
- Point3d*
- Point3d::Point3d(Point3d* this, bool __most_derived,
- float x = 0.0, float y = 0.0, float z = 0.0) {
- if(__most_derived != false) this->Point::Point();
- //虚拟继承下两个虚表的设定
- this->__vptr_Point3d = __vtbl_Point3d;
- this->__vptr_Point3d__Point = __vtbl_Point3d__Point;
- this->z = rhs.z;
- return this;
- }
复制代码 而并非如下传统的扩张
- //c++伪码
- Point3d*
- Point3d::Point3d(Point3d* this,
- float x = 0.0, float y = 0.0, float z = 0.0) {
- this->Point::Point();
- this->__vptr_Point3d = __vtbl_Point3d;
- this->__vptr_Point3d__Point = __vtbl_Point3d__Point;
- this->z = rhs.z;
- return this;
- }
复制代码 两种扩张的不同之处在于参数__most_derived,在更加深条理的继承情况下,比方Vextex3d,调用Point3d和Vertex的constructor时,总会将该参数设置为false,于是就克制了两个constructors对Point constructor的调用操作。 比方:
- //c++伪码
- Vextex3d*
- Vextex3d::Vextex3d(Vextex3d* this, bool __most_derived,
- float x = 0.0, float y = 0.0, float z = 0.0) {
- if(__most_derived != false) this->Point::Point();
- //设定__most_derived为false
- this->Point3d::Point3d(false, x, y, z);
- this->Vertex::Vertex(false, x, y);
- //设定vptrs
- return this;
- }
复制代码 当我们界说
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的调用次序是
- Point(x, y);
- Point3d(x, y, z);
- Vertex(x, y, z);
- Vertex3d(x, y, z);
- PVertex(x, y, z);
复制代码 假设每个class都界说了一个virtual function size();返回该class的大小。我们来看看界说的PVertex constructor
- PVertex::Pvertex(float x, float y, float z)
- : _next(0), Vertex3d(x, y, z), Point(x, y) {
- if(spyOn)
- cerr << "Within Pvertex::PVertex()"
- << "size: " << size() << endl;
- }
复制代码 编译器怎样包管恰当的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可能被扩张成
- PVertex*
- PVertex::Pvertex(PVertex*this, bool __most_derived,
- float x, float y, float z) {
- //有条件调用virtual base class constructor
- if(__most_derived != false) this->Point::Point();
- //无条件调用上一层base class constructor
- this->Vertex3d::Vertex3d(x, y, z);
- //设定vptr
- this->__vptr_PVertex = __vtbl_PVertex;
- this->__vptr_Point3d__PVertex = __vtbl_Point3d__PVertex;
- //执行程序员的代码
- if(spyOn)
- cerr << "Within Pvertex::PVertex()"
- << "size: "
- //虚拟机制调用
- << (*this->__vptr_PVertex[3].faddr)(this)
- << endl;
- return this;
- }
复制代码 下面是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企服之家,中国第一个企服评测及商务社交产业平台。 |