[@toc]
在c++中,会遇到菱形继承的情况。
内存情况
c++内存模型告诉我们,当我们创建一个派生类对象B时,同时会创建一个父类的A对象。
对于上面的这种菱形模型, B C 都创建时,都会创建一个A,所以会有两个A。这是造成了内存上的浪费。
测试代码如下:
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout << " A ctor \n";
}
};
class B: public A
{
public:
B()
{
cout << " B ctor \n" ;
}
};
class C : public A
{
public:
C()
{
cout << " C ctor \n";
}
};
class D : public B,C
{
public:
D()
{
cout << " D ctor \n";
}
};
int main()
{
D d;
}
输出代码是
A ctor
B ctor
A ctor
C ctor
D ctor
可以看出,A对象被创建了两次。
虚拟继承
为了解决这个问题,c++引入了虚拟继承,即 BC virtual 继承A
代码如下
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout << " A ctor \n";
}
};
class B: virtual public A
{
public:
B()
{
cout << " B ctor \n" ;
}
};
class C : virtual public A
{
public:
C()
{
cout << " C ctor \n";
}
};
class D : public B,C
{
public:
D()
{
cout << " D ctor \n";
}
};
int main()
{
D d;
}
这种情况下的输入情况是
A ctor
B ctor
C ctor
D ctor
可以看到A 只被构建了一次。
这就完了吗? 当然不是,接下来分析更复杂的情况,比如
- A是在哪里被构建的?
- A 如果带参数会是怎么样的?
发个广告 2023 应届生招聘,学历要求 985硕士,我可以帮忙内推
招聘
原理分析
A 在哪里构建?
在B C D的视角中,A的constructor 我们称为virtual base class constructor。对于他的初始化,有这么个规律 只有当一个完成的class object被定义出来时,它才会被调用。如果object只是某个完整object的subobject,那么他不会被调用
在这里,当我们初始化D时,B,C都是subobject,所以他们的构造函数都无法触发A的构建。开始构建D时,才会触发A的构建。
因此,A是在D 开始构建时构建的。
在书中,我们看到维持这个机制的做法是,在 BC 的构造函数中设置一个检查flag,只有当flag为true时,才会构建A。
B b;
这种情况,flag是有B设置,为true. A 会被构建出来。
D d;
这种情况,在D的构造函数中,D会把flag设为false,然后构建B。 B也就无法构建A了。
具体参考 深度探索c++ 第五章 构造析构拷贝语义 , 代码就不贴了。
有参数情形
如下案例代码
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout << "A ctor none \n" << endl;
}
A(int x1): x(x1)
{
cout <<"A ctor x :" << x <<endl;
}
virtual void hello()
{
}
int x;
};
class B : virtual public A
{
public:
B(int x1, int y1):A(x1),y(y1)
{
cout << " B ctor y: " << y << endl;
}
int y;
};
class C : virtual public A
{
public:
C(int x1, int y1):A(x1),y(y1)
{
cout << " C ctor y: " << y << endl;
}
int y;
};
class D: public B, public C
{
public:
// scenario1
D(int x, int y) : B(x,y), C(x,y)
{
cout <<" D x y :" << x << " " << y << endl;
}
// scenario 2
//D(int x, int y) : B(x,y), C(x,y), A(x)
// {
// cout <<" D x y :" << x << " " << y << endl;
// }
};
int main()
{
D c(1,10); //
return 1;
}
主要观察D 的两种构造函数,如果是scenario1
, 虽然B C 中使用了 A(x)的构造函数,但是并不是真正的被调用。原因是BC此时是subobject,不会触发A的构建。因此会A()会被触发。
scenario2
, 此时A(x)会被触发。
输出结果是:
scenario1:
A ctor none
B ctor y: 10
C ctor y: 10
D x y :1 10
scenario2
A ctor x :1
B ctor y: 10
C ctor y: 10
D x y :1 10