文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>常见错误53: 对于虚基类子对象进行默认初始化----读书笔记《c++ gotchas》...

常见错误53: 对于虚基类子对象进行默认初始化----读书笔记《c++ gotchas》...

时间:2010-08-17  来源:lzueclipse

一个class对象中的虚基类子对象和非虚基类子对象,布局不同。

非虚基类子对象如同它是派生类中的一个普通数据成员,可以出现多次:

class A {memebers};

class B : public A { members };

class C : public A {members };

class D : public B, public C { members };

而虚基类类型子对象在派生类对象中出现一次:

class A {memebers};

class B : public virtual A { members };

class C : public virtual A {members };

class D : public B, public C { members };


为掩饰方便,此处使用的是相当老旧的指针式虚基类实现:在A类型子对象本来应该出现的地方,放置了一个指向A类型子对象的指针。

在新的编译器视线中,这种手法一般是不用的,取而代之的是一个偏移量,或者是虚函数表中的附加信息来完成。

典型地,虚基类子对象是附在完整对象之后。

上例中,完整对象是D,因此虚基类子对象A是放在D的数据成员之后。

只有最深派生类才知道虚基类子对象的精确地址。

在最深派生类类型为B的情况下,就是B的构造函数来初始化虚基类子对象A,并将指针指向它:

B::B(int arg)

    : A(arg) {}

 图5-2中,最深的派生类是D,所以D的构造函数负责初始化虚基类子对象A,并在B和C中准备好指向该对象的指针,另外还将完成

直接继承基类B和C的子对象的初始化工作:

D::D(int arg)

    : A(arg), B(arg), C(arg+1) {}

这样,一旦虚基类子对象被D的构造函数初始化,它就不会被B或C的构造函数再初始化一遍(一种编译器可能采用的实现策略

是它会向B或C的构造函数传递一个flag,或是A类型的指针,提醒不要再把A类型子对象再初始化一遍)。

再来看一个D的构造函数:

D::D()

    : B(11), D(12) {}

D的构造函数仍然初始化了虚基类子对象A,通过隐式调用A的默认构造函数来完成;当调用B和C的构造函数时,也不会再把

A的子对象初始化一遍。

我们知道复制赋值运算符应该和对应的构造函数有相同的语义。下面我们来讨论复制赋值,使得它也不会对虚基类多次

赋值:

意图设计成虚基类的class,最好是把它们设计成“接口类”(interface class)——不包含数据成员,

一般而言其成员函数(也许唯一例外的是析构函数)全是纯虚函数,不声明任何构造函数:

class A {

public:

    virtual ~A();

    virtual void op1() = 0;

    virtual int op2(int src, int dest) = 0;

    //...

};

inline A::~A() {}

对于复制赋值而言,编译器提供的复制赋值运算符可能会,也可能不会把一个虚基类子对象赋值多次;

如果所有的虚基类都是接口类的话,那么复制操作符肯定实现为空操作。(因为指向虚函数表的指针,不受赋值操作符的影响,

只在初始化时设置)。如此一来,多次赋值也不会导致缺陷。

考虑图5-1所示的class D的实现,它包含两个A类型的子对象,在此情形中,我们撰写一个复制赋值运算符,

该运算符基于直接基类(immediate base class)实现:

D & D::operator = (const D &rhs) {

    if(this != &rhs) {

        B::operator=(*this);//对B类型子对象赋值,同时完成A类型子对象赋值

        C::operator=(*this);//对C类型子对象赋值,同时完成A类型子对象赋值

        //D的特定数据成员赋值

    }

}

这种分层的赋值实现对于存在虚继承的情况下,玩不转。

最深派生类应该完成虚基类子对象赋值的同时,应该阻止对于该虚基类子对象的重复赋值行为:

D & D::operator = (const D &rhs) {

    if(this != &rhs) {

        A::operator = (*this);//对虚基类子对象A赋值

        B::nonvirtAssign(*this);//对B类型子对象赋值,除了A部分

        C::nonvirtAssign(*this);//对C类型子对象赋值,出了A部分

        //对D特有数据成员赋值

    }

}

这里,在B和C引入了特殊的assignment-like member function,它们完成的操作和复制赋值运算符很相似,只是把

虚基类复制那部分赋值去掉了。但是有个问题,引入了复杂性,要密切关注整个继承谱系,任何继承谱系的改动,

都会带来重写D的实现的需求。打算用作虚基类的class最好还是实现为接口类。

虚基类子对象在一个完整对象中的布局隐含着一个推论,那就是不允许使用static_cast向下强制类型转换(static downcast)

,将虚基类对象转换至其派生类类型。

A *ap = gimmeanA();

D *dp = static_cast<D *> (ap);//错误

dp = (D *) ap;//错误

使用reinterpret_cast把一个虚基类转换成某个派生类是合法的,但结果可能是一个无效地址。

唯一可靠的是dynamic_cast:

if(D *dp = dynamic_cast<D *> (ap)) {

    //正确

}

相关阅读 更多 +
排行榜 更多 +
辰域智控app

辰域智控app

系统工具 下载
网医联盟app

网医联盟app

运动健身 下载
汇丰汇选App

汇丰汇选App

金融理财 下载