第五章:构造函数和拷贝赋值运算符

一、构造函数

  1.虚继承体系下的构造函数

  为了满足虚继承只有一个虚基类子对象的条件(即由最顶层的派生类调用虚基类的构造函数来初始化虚基类子对象),编译器在构造函数中加入一个参数bool  _most_derived来判断是否是顶层派生类,如果是则调用拷贝构造函数。可能的伪码如下。

Point3d *Point3d::Point3d(Point3d *this,bool _most_derived)
{
    if(_most_derived)
        this->Point::Point(x,y);
    ...
}

 

  2.指向虚函数表的指针vptr的初始化

  vptr初始化顺序:在基类构造函数调用操作之后(包括虚基类和上一层基类的构造函数),在成员初始化列表中所列的成员初始化操作之前。当然也是由编译器完成。

  产生上述结果的原因是:如果我们在类Point3d的构造函数中调用一个虚函数size(),我们当然希望他是调用自己的那个size()而不是基类的。此时可以用静态调用Point3d::size(),但是如果我们在size()中间再调用一个虚函数insize()呢,那insize()不能用静态调用。此时只要还没有初始化Point3d的虚函数表指针,那么虚函数自然无法实现,即insize()只能调用Point3d::insize()了。

  注意这个机制还不能处理以下情况:如果在Point3d的构造函数的初始化列表中,要给Point的构造函数提供一个虚函数作为参数,这样是错误的,因为vptr不是没有设置好,就是被设置指向错误的类(成员还没初始化)

Point3d::Point3d():Point(size()){};  //错误

  

二、拷贝赋值运算符

  1.在虚继承情况下,需要面对一个问题,执行最顶层派生类的拷贝赋值运算符时对虚基类的拷贝赋值运算符的重复调用。(当时的编译器没有很好地处理这件事

  因为除了构造函数外,我们无法使用成员初始化列表,所以下面的代码不正确。

Point3d& Point3d::operator(const Point3d &p3d)       //错误
:Point(p3d),z(p3d._z){}

  并且也不能像构造函数那样添加额外的参数,因为构造函数只需给本题添加参数即可,但是拷贝赋值运算符允许取地址即通过函数指针的方式调用,所以就要根据其继承体系添加过多的参数,这显然是不可取的。

  另一种方法是编译器产生分化函数split function,对不同情况产生不同的函数。编译器显然可以做到这样,但是类的设计者却很难做到(尤其是拷贝赋值运算符中又调用虚函数的情况)。

  作者写书的时候市面上的编译器都没有处理这个问题,而是多次赋值(这样会造成赋值的语义错误,光看代码会觉得虚基类数据被来回的复制)。

  有一种方法可以保证最顶层的类调用虚基类的拷贝赋值运算符(这样满足虚基类的含义),就是在最顶层的拷贝赋值运算符的最后一行调用虚基类的拷贝赋值运算符,这样虽然不能省略子对象的多重拷贝,但是起码含义正确。还一种方法是把虚基类子对象拷贝到一个分离的函数中,并根据调用路径来条件化调用它。

posted @ 2021-06-28 21:49  放不下的小女孩  阅读(116)  评论(0)    收藏  举报