注册 登录
编程论坛 C++教室

[求助]虚继承的问题

zkkpkk 发布于 2007-05-31 09:27, 2513 次点击

class Usable;
class Usable_lock
{                             //这家伙把这个类的构造函数声明为私有并把Usable类声明为他的友元
    friend class Usable;   
private:        
    Usable_lock() {}        
    Usable_lock(const Usable_lock&) {}   
};
           
class Usable : public virtual Usable_lock      //虚继承Usable_lock类
{    // ...   
public:        
    Usable();        
    Usable(char*);        // ...   
};            
               
class DD : public Usable
{ };
int main()
{   
    Usable a;         
    DD dd;       //错误!因为 DD 不能访问 Usable_lock 的构造函数,也就创建不了 dd,防止了Usable被继承
    return 0;
}
//这里的如果Usable不是虚继承基类的话DD dd;就不会错,虚继承决定了什么?!
21 回复
#2
aipb20072007-05-31 11:59

现在才看到你的问题,帮你看了下。
你说 “这里的如果Usable不是虚继承基类的话DD dd;就不会错,虚继承决定了什么?!”

你把
class Usable : public /*virtual*/ Usable_lock //虚继承Usable_lock类
{ // ...
public:
Usable(){} //这里改下,加上定义
Usable(char*); // ...
};

这样,代码就可以通过,就没有达到防止Usable 被继承的作用。

////////////////////////////////////////////////////////////////////////////////
先说说上面的代码,包括继承原理:

首先,把我们不想被继承的类作为一个派生类去继承一个没哟意义的Usable_lock类,注意,这个类显示的把构造函数
声明为私有,然后把Usable作为友员类,这就是关键。
再来,继承中派生类创建的过程:首先要调用父类的构造函数,即先建立一个父类的对象。
这里Usable类是公有继承,所以是访问不到私有的构造函数,但是作为友员是可以的,所以消除了这个限制。由此,我们的Usable类是可以被创建的。

到这里,看看为什么普通的非虚拟继承,能使dd的创建合法。
首先,按照我们先前说讲,DD类公有继承Usable类,创建dd对象的过程是先调用父类构造函数(Usable类构造函数),这一步合法,因为可以访问到Usable的构造函数。这时就要创建一个Usable对象,要创建Usable 对象,就要调用Usable 的父类构造函数,就来到Usable_lock类,开始我们说了,由Usable访问Usable_lock是没有问题的。所以这样一来,dd对象就被正确的创建了。他间接的访问过Usable_lock。

////////////////////////////////////////////////////////////////////////
下面说说BJ在这个例子中是怎样通过虚拟技术防止Usable类被继承的:
虚拟继承,通过在声明派生类继承父类时加上virtual来指定。
虚拟继承的作用:当系统碰到多重继承的时候保证继承类成员函数的唯一性。
怎么理解他的作用,看下面这个图1:
class base //基类
class derive1 : public base
class derive2 : public base

class derive3 : public derive1,public derive2

这种情况下,derive3就同时拥有了两套base的函数。所以在调用时产生的模糊性会使程序出错。具体我就不谈那么多了。

解决这个问题,就用到了虚拟技术。
修改上面的代码 图2:
class base //基类
class derive1 : virtual public base
class derive2 : virtual public base

class derive3 : public derive1,public derive2

采用虚拟继承的时候,在derive3中,一旦一次继承了base类,再次继承base的时候就会忽略重复的base类。所以保证继承类成员函数的唯一性。

///////////////////////////////////////////////////////////////////
在这个问题中,BJ大师用到的不是这个原理,而是由此产生的一个构造函数调用顺序问题。
在非虚拟继承中,比如图1中。在构造derive3时,其父类调用顺序为derive1-derive2-base;
然后在虚拟继承中。比如图2中。在构造derive3时,其父类调用顺序为base-derive1-derive2;

同样,这样的顺序,作用显而易见,是防止base被多次继承。

再回到我们最初的话题,在DD类创建对象dd时,最先被调用的是Usable_lock类的构造函数,但是,由于Usable_lock构造函数声明被私有,就出现不能访问的错误。由此,是不能创建DD对象的。用这个方法,完成了防止了Usable被继承。



//////////////////////////////////////////////////////////////////////
仓促中作了些对继承和虚拟继承的分析。不对的地方和需要完善的地方请指出。
很欣赏楼主这种思考精神

#3
天下第二刀2007-05-31 13:55
//这里的如果Usable不是虚继承基类的话DD dd;就不会错,虚继承决定了什么?!


就是改变了构造函数调用的顺序???
#4
天下第二刀2007-05-31 14:09
不错, 分析很到位, 学习了
#5
zkkpkk2007-05-31 15:43
这种情况下,derive3就同时拥有了两套base的函数。所以在调用时产生的模糊性会使程序出错。具体我就不谈那么多了。

也不用谈了,菱形的二义性,基本了解
谢谢斑竹的分析
补足了我继承与派生方面的知识,其实我记得教材有的,但是怎么翻也翻不到,百度出来也是一堆乱七八糟

[此贴子已经被作者于2007-5-31 15:50:48编辑过]

#6
raulxxyuer2007-05-31 15:49
#7
yuyunliuhen2007-05-31 21:06

恩,分析很透彻,

#8
wfpb2007-05-31 22:04

在存在虚拟继承的继承结构层次中:子类的构造函数必须在初始化列表中将他所有的直接基类以及非直接虚基类都进行构造(如果是默认构造可以忽略)。
如下:

#include <iostream>
using namespace std;


class A
{
public:
    A(int a){cout<<\"A::Constructor\"<<endl;}
};
class B:public virtual A
{
public:
    B(int b):A(b){cout<<\"B::Constructor\"<<endl;}
};
class C:public B
{
public:
    C(int c):A(c),B(c){cout<<\"C::Constructor\"<<endl;}//很显然,A(c)要在初始化列表中写出来
                                                     //因此就是C(int)调用A(int),所以在LZ的代码中失败!
};
void main()
{
    C c(0);
}


[此贴子已经被作者于2007-5-31 22:05:49编辑过]

#9
zkkpkk2007-05-31 22:31
以下是引用wfpb在2007-5-31 22:04:45的发言:

在存在虚拟继承的继承结构层次中:子类的构造函数必须在初始化列表中将他所有的直接基类以及非直接虚基类都进行构造(如果是默认构造可以忽略)。
如下:

#include <iostream>
using namespace std;


class A
{
public:
    A(int a){cout<<\"A::Constructor\"<<endl;}
};
class B:public virtual A
{
public:
    B(int b):A(b){cout<<\"B::Constructor\"<<endl;}
};
class C:public B
{
public:
    C(int c):A(c),B(c){cout<<\"C::Constructor\"<<endl;}//很显然,A(c)要在初始化列表中写出来
                                                     //因此就是C(int)调用A(int),所以在LZ的代码中失败!
};
void main()
{
    C c(0);
}



我的例子是BJ大叔防止自己的类被继承的本意就是要DD dd不成功,我只是问虚继承的问题

#10
wfpb2007-06-02 01:35
是啊,我是说明为什么会调用失败的原因,因为子类要在初始化列表中构造他所有的直接基类以及非直接虚基类。
而Usable_lock()这个非直接虚拟基类的构造函数是无参构造函数,可以省略,所以在class DD中就没有显示写出,但是其实是存在的,也即是说DD类的构造函数是这样的:
DD:Usable_lock(),Usable(){}
#11
zkkpkk2007-06-02 22:48
以下是引用wfpb在2007-6-2 1:35:32的发言:
是啊,我是说明为什么会调用失败的原因,因为子类要在初始化列表中构造他所有的直接基类以及非直接虚基类。
而Usable_lock()这个非直接虚拟基类的构造函数是无参构造函数,可以省略,所以在class DD中就没有显示写出,但是其实是存在的,也即是说DD类的构造函数是这样的:
DD:Usable_lock(),Usable(){}

之前已经明白了......

#12
wfpb2007-06-03 15:15
哦,不好意思,我累赘了!
#13
zkkpkk2007-06-03 16:40
以下是引用wfpb在2007-6-3 15:15:36的发言:
哦,不好意思,我累赘了!

没有啊,我只是以为你认为我问怎样才能使之编译通过

#14
kisscjy2007-06-03 21:41
学到东西了~~~谢谢~~~

原来虚继承会调转继承顺序滴~~~
#15
zkkpkk2007-06-04 09:46
以下是引用kisscjy在2007-6-3 21:41:32的发言:
学到东西了~~~谢谢~~~

原来虚继承会调转继承顺序滴~~~

你的签名真有趣

#16
zakker2007-06-04 13:42

class base //基类
class derive1 : public base
class derive2 : public base
class derive3 : public derive1,public derive2
===================================================
class base //基类
class derive1 : virtual public base
class derive2 : virtual public bass
class derive3 : public derive1,public derive2
=====================================================
我认为在非虚继承的时候 在构造derive3时,其父类调用顺序为derive1-base-derive2-base
构造函数顺序是 base-derive1-base-derive2
在虚继承的时候,在构造derive3时,其父类调用顺序为derive1-base-derive2
构造函数顺序是 base-derive1-derive2
并非你说的那样

#17
aipb20072007-06-04 14:17

楼上的真是细心,是我没叙述清楚,我这里说的是一个父类访问顺序,并非构造函数执行的顺序。
比如非虚继承,还是那个例子,
在创建derived3时,顺序(访问顺序)是derived1-base-derived1-derived2-base-derived2;
红色部分为构造函数执行顺序。
而虚继承中:
在创建derived3时,顺序(访问顺序)是base-derived1-derived2.
同样红色部分为构造函数执行顺序。

这样就可以根据访问顺序,看到是谁在访问父类,所以解决最初那个问题。

由于那时写的匆忙,没有说明清楚。见谅了!谢谢指出。

#18
zakker2007-06-06 00:31

class Usable;
class Usable_lock
{ //这家伙把这个类的构造函数声明为私有并把Usable类声明为他的友元
friend class Usable;
private:
Usable_lock() {}
Usable_lock(const Usable_lock&) {}
};

class Usable : public virtual Usable_lock //虚继承Usable_lock类
{ // ...
public:
Usable();
Usable(char*); // ...
};

class DD : public Usable
{ };
int main()
{
Usable a;
DD dd; //错误!因为 DD 不能访问 Usable_lock 的构造函数,也就创建不了 dd,防止了Usable被继承
return 0;
}
//这里的如果Usable不是虚继承基类的话DD dd;就不会错,虚继承决定了什么?!
===================================================================================
楼上的 我有2个问题请教:
1,请问下就楼主的题目而言,在创建DD dd的时候,DD Usable Usable_lock这3个类访问的顺序和构造的顺序是什么,
我看不出用了多次Usable_lock的构造函数来阻止 Usable的再继承,我觉得只用了一次;
2,楼主指出,如果不是虚继承的话 DD dd就是正确的,我试过确实是,请问下DD 并不是Usable_lock的友元,却为什么能访问Usable_lock的私有段,难道说 Usable和它基类的友元关系被继承下来了?
#19
aipb20072007-06-06 10:31
回复:(zakker)class Usable;class Usable_lock { ...

你的两个问题确实就是一个问题,你不明白,DD类是怎么和Usable_lock类是怎样作用的。

首先,在非虚继承时,能通过编译。这时,在创建DD对象时,要创建其直接基类子对象,所以由DD类访问到Usable类,要创建usable类对象,要创建其直接基类Usavle_lock类的子对象,所以由Usable访问Usable_lock类,所以可以成功。

而在虚继承中,DD类会首先访问Usable_lock类,所以不成功。

#include <iostream>
using namespace std;
struct base{
base(){cout << "construct base\n";}
};

struct derived1 : /*virtual*/ base{
derived1(){cout << "construct derived1\n";}
};

struct derived2 : /*virtual*/ base{
derived2(){cout << "construct derived2\n";}
};

struct derived3 : derived1,derived2{
derived3(){cout << "construct derived3\n";}
};

int main(){
derived3 obj;
}

把注释部分加上和去掉的不同情况下,去单步调试下,帮助你理解。
#20
zkkpkk2007-06-07 22:28
回复:(zakker)class Usable;class Usable_lock { ...
因为继承顺序的问题,所以DD通过他“父亲”调了“爷爷”的函数
#21
wrb123452007-06-08 16:02
谢谢斑竹
学习了新东西
#22
neverDie2007-06-08 17:25
看懂了,恩,很不错!
1