5.2 继承体系下的对象构造

当我们定义一个 object 如下:

T object;

实际会发生什么呢? 如果T有一个 constructor(不论user还是编译器提供),它会被调用。这很明显,而不明显的是, constructor 的调用真正伴随了什么?
Constructor 可能内带大量的隐藏码, 因为编译器会扩充每一个 constructor, 扩充程度视 class T 的继承体系而定。一般而言编译器所做的扩充操作大约如下:

1.记录在 member initialization list 中的 data members 初始化操作会被放进 constructor 的函数本身, 并以 members 的声明顺序作为顺序。

2.如果有一个 member 并没有出现在 member initialization list 之中, 但它有一个 default constructor, 那么该 default constructor 必须被调用.

3.在那之前, 如果 class object 有 virtual table pointers 它们必须被设定初值, 指向适当的 virtual tables。

4.在那之前, 所有上一层的 base class constructors 必须被调用, 以 base class 的声明顺序为顺序 (这个与 member initialization list 中的顺序没关系):

4.1 如果 base class 被列于 member initialization list 中, 那么任何明确指定 的参数都应该被传递过去.
4.2 如果 base class 没有列于 member initialization list 中, 而它有 default constructor(哪怕是 default memberwise copy constructor), 调用之.
4.3 如果 base class 是多重继承下的第二或后继的 base class , 那么 this 指针 必须有所调整

5.在那之前, 所有 virtual base class constructors 必须被调用, 由最深到最浅:

5.1 如果 class 被列于 member initialization list 中, 那么如果有任何明确指定 的参数, 都应该传递过去, 若没有列于 list 之中但是有一个 default constructors 的, 也该调用之.
5.2 此外, class 中的每一个 virtual base class subobject 的偏移量(offset) 必须在执行期可被存取.
5.3如果 class object 是最底层的 class, 其 constructors 可能被调用, 某些用以支持这个行为的机制必须被放进来.

在这个部分中, 从 C++ 语言对 classes 所保证的语意这个角度来探讨 constructors 扩充的必要性。再次以 Point 为例, 并为它增加一个 copy constructor, 一个 copy operator, 一个 virtual destructor 如下:

class Point
{
public:
    Point(float x = 0.0, float y = 0.0);
    Point(const Point&);
    Point& operator=(const Point&);
    virtual ~Point();
    virtual float Z();
protected:
    float _x, _y;
}

在分析以这个 class 为 base 的继承体系之前, 先看这么个类:

class Line
{
    Point _begin, _end;
public:
    Line(float = 0.0. float = 0.0, float = 0.0, float = 0.0);
    Line(const Point&, const Point&);
    draw();
    //...
};

这个类中每个 explicit constructor 都会被扩充, 以调用其两个 member class objects (Point class)的 constructors. 如果我们定义 constructor 如下:

Line::Line(const Poit &begin, const Point &end):_end(end), _begin(begin){ }
// 此copy constructor 会被扩展为
Line* Line::Line(Line *this, const Point &begin, const Point &end)
{
    this->_begin.Point::Point(begin);
    this->_end.Point::Point(end);
    return this;
}

由于 Point 声明了一个 copy constructor, 一个copy operator, 以及一个 destructor(本例是virtual), 所以 Line class 的 implicit copy constructor,copy operator 和 destructor 都是 non-trivial.
当程序员写下 Line a; 时, implicit Line destructor 就会被合成出来(如果 Line 派生出 Point, 那么合成出的 destructor 将会是 virtual, 然由于 Line 只是 has Point objects 而非继承自 Point, 所以被合成出的 destructor 只是 nontrivial 而已, 并不是说立刻也把 Point 的 destructor 也合成出来). 在其中, 它的 member class objects 的会被调用:

inline void Line::~Line(Line *this)
{
    this->_begin.Point::~Point();
    this->_end.Point::~Point();
}

当然, 如果 Point destructor 是 inline 函数, 那么每一个调用操作会在调用地点被扩展开来. 注意, 虽然 Point destructor 是 virtual , 但其调用操作会被静态(加上作用域::)的决议出来.
类似的, 当写下:

Line b = a;

implicit Line copy constructor 会被合成出来,成为一个inline public member。
当写下:

a = b;

implicit copy assignment operator被合成出来,成为 inline public member。

作者在改写 cfront 时, 注意到在产生 copy operator 的时候, 并没有用如下的条件语句筛选:

if(this == &rhs){ return *this; }
于是 cfront 会做多余的拷贝操作:
Line *p1 = &a;
Line *p2 = &b;
*p1 = *p2;

上述的重复操作虽然安全但是累赘, 因为并没有任何有关资源释放的行动。 一个有程序员供应的 copy operator 中忘记检查自我指派操作是否失败。

虚拟继承

考虑下面这个虚拟继承:(继承自 Point)

class Point3d: public virtual Point
{
    Point3d(float x = 0.0, float y = 0.0, flloat z = 0.0): Point(x, y), _z(z){ }
    Point(const Point3d &rhs): Point(rhs), _z(rhs._z){ }
    Point3d& operator=(const Point3d&);
    ~Point3d();
    virtual float Z() { return _z; }
protected:
    float _z;
};

传统的 constructor 扩充现象并没有用, 因为 virtual base class 的共享性之故:

//不合法的 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 扩充内容有什么错误?

个人的理解是菱形继承中, subobject 并不负责调用最底层 class 的 constructor, 负责调用的 class 应该是菱形底部那个近的 class, 因此扩充码之中不该有对 Point constructor 的调用. 我也希望与大家交流下你对这个扩充码的看法. 我的看法具体理由如下文:

考虑以下三种派生情况:

class Vertex: virtual public Point{ ... };
class Vertex3d: public Point3d, public Vertex{ ... };
class PVertex: public Vertex3d{ ... };

image
Vertex 的 constructor 必须也调用 Point 的constructor。
然而当 Point3d 和 Vertex 同为 Vertex3d 的 subobjects 时, 它们对 Point constructor 的调用操作一定不可以发生;取而代之的是一个最底层的 class, Vertex3d 有责任将 Point 初始化, 而后的继承则由 PVertex 来完成被共享的 Point subobject 的构造。

传统策略判断现在要不要将 virtual base class 初始化, 导致了 constructor 中有更多的扩充的内容。判断 virtual base class constructor 该不该被调用,constructor 的函数本体必须条件式地测试传进来的参数, 然后决定调不调用相关的 virtual base class constructors. 下面就是 Point3d 的 constructor 扩展内容:

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 的调用操作.

//在 virtual base class 情况下的 constructor 扩展内容
Vertex3d* Vertex3d::Vertex3d(Vertex3d *this, bool _most_derived, float x, float y, float z)
{
    if(_most_derived != false){ this->Point::Point(x, y); }
	// 调用上一层 base class
	// 设定 _most_derived == false
    // 不是最底层的 class, 就只能各取所需
    this->Point3d::Point3d( false, x, y, z );
    this->Vertex::Vertex( false, x, y );

    //设定 vptrs
    //安插 user code

    return this;
}

当我们定义:

Point3d origin;

Point3d constructor 可以正确的调用其 Point virtual base class subobject。
而当我们定义:

Vertex3d cv;

???
Vertex3d constructor 正确地调用 Point constructor。Point3d 和 Vertex 的 constructors 会做除了对 Point 的调用操作之外该做的事。
许多人可能已经注意到某种状态:virtual base class constructors 的被调用有着明确的定义: 只有当一个完整的 class object 被定义出来时(Point3d origin;), 它才会被调用; 如果 object 只是某个完整的 object 的 subobject, 它就不会被调用。
以这一点为杠杆, 可以产生更有效率的 constructors. 某些编译器把每个 constructor 分裂为二, 一个针对完整的 object, 另一个针对 subobject. 完整的 object 版无条件的调用 virtual base constructors, 设定所有的 vptrs 等等; subobject 版则不调用 virtual base constructors 和 vptrs。 constructor 的分裂可以带来速度的提升。
???

vptr 初始化语义学

当定义一个PVertex时,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 pv;
Point3d p3d;
Point *pt = &pv;

下传回PVertex的大小:

pt = &p3d;
pt->size();

进一步,假设这个继承体系中每一个constructors内含一个调用操作:

Point3d::Point3d(float x, float y, float z):_x(x),_y(y),_z(z)
{
	if(spyOn){ cerr<<"Within Point3d::Point3d() size:"<<size()<<endl; }
}

////////////////////////后面没看//////////////////////////

posted @ 2021-04-23 14:52  点|滴  阅读(12)  评论(0)    收藏  举报