多重继承和访问不明确的元素

Multiple inheritance and accessing ambiguous elements

本文关键字:元素 不明确 访问 多重继承      更新时间:2023-10-16

我试图通过多重继承和模板化来实现一些运行时类型信息技巧,但遇到了一个模糊性问题。

首先,我所说的一个非常基本的例子:

#include <memory>
struct A { };
struct B : public A { };
struct C : public B, public A { };
int main() {
std::shared_ptr<A> p0 = std::make_shared<B>(); // possible
std::shared_ptr<A> p1 = std::make_shared<C>(); // impossible
// Here the compiler obviously whines "‘A’ is an ambiguous base of ‘C’"
// but if we're especially naughty, we can circumvent it with a void* cast
// we still can't really use this pointer to anything though.
A* p2 = static_cast<A*>((void*)new C()); 
}

正如评论所暗示的那样,我可能特别顽皮,完全无视这种类型。这种恶作剧会让我直接进入开发人员的地狱,而且无论如何都不会让我随心所欲地使用指针,所以我实际上对std::shared_ptr<B>的基础很满意。但第二个(稍微接近现实(例子表明,这种妥协是不够的:

#include <iostream>
#include <memory>
int current_val = 0;
struct A {
int runtime_val;
A(int v) : runtime_val(v) 
{ 
std::cout << "ACtor with arg: " << v << std::endl; 
}
};
template<typename T>
struct B : public A {
static const int global_val;
B() : A(global_val) {}
};
template<typename T>
const int B<T>::global_val{current_val++};
struct X : public B<X> {};           // Base 
struct C : public X, public B<C> {}; // Leaf
struct D : public X, public B<D> {}; // Leaf
int main() {
std::cout << "Xval: " << B<X>::global_val << std::endl; // 0
std::cout << "Dval: " << B<D>::global_val << std::endl; // 1
std::cout << "Cval: " << B<C>::global_val << std::endl; // 2
// std::shared_ptr<A> px = std::make_shared<X>(); // Impossible because of ambiguity
std::shared_ptr<X> p0 = std::make_shared<X>(); // I'm fine with this, really
std::shared_ptr<X> p1 = std::make_shared<D>();
std::shared_ptr<X> p2 = std::make_shared<C>();
std::cout << "p0 val: " << p0->runtime_val << " (X)" << std::endl; // 0     :)
std::cout << "p1 val: " << p0->runtime_val << " (D)" << std::endl; // 0     :(
std::cout << "p2 val: " << p0->runtime_val << " (C)" << std::endl; // 0    >:(
}

在本例中,我使用B<T>::global_val作为给定类型<T>的运行时类型信息。当程序运行时,我得到以下输出(为了清晰起见,我添加了一些额外的注释(:

Xval: 0
Dval: 1
Cval: 2
ACtor with arg: 0  -- (p0 initialization)
ACtor with arg: 0  -- (p1 initialization (X base))
ACtor with arg: 1  -- (p1 initialization (D base))
ACtor with arg: 0  -- (p2 initialization (X base))
ACtor with arg: 2  -- (p2 initialization (C base))
p0 val: 0 (X)
p1 val: 0 (D)
p2 val: 0 (C)

v表似乎只想指向我的DC类的X基。如何确保DC实例的runtime_val将指向继承树的叶,而不是基?

附言:我试过让X基类纯粹是虚拟的,但没有成功。

std::shared_ptr<A> p1 = std::make_shared<C>(); // impossible

为了澄清为什么这是不可能的,编译器不可能知道您打印的A碱基指向哪一个。

如果您打印指向B碱基的A碱基,则可以通过显式转换:

std::shared_ptr<A> p1 = std::static_pointer_cast<B>(std::make_shared<C>());

但由于歧义,确实没有办法提及直接的A碱基。你可以在另一个基础上引入额外的包装器:

struct A          {};
struct B  : A     {};
struct B2 : A     {};
struct C  : B, B2 {};
std::shared_ptr<A> p1 = std::static_pointer_cast<B >(std::make_shared<C>());
std::shared_ptr<A> p2 = std::static_pointer_cast<B2>(std::make_shared<C>());

这看起来不对:

struct B : public A { };
struct C : public B, public A { };

C是B,而B又是a。因此,要求C也成为A感觉是错误的。

我的编译器警告:

1>D:\projects\scratch\cpp(6,19(:警告C4584:"C":基类"A"已经是"B"的基类1> D:\projects\scratch\sscratch.cpp(4(:消息:请参见"A"的声明1> D:\projects\scratch\sscratch.cpp(5(:消息:请参见"B"的声明

删除重复继承修复了第一个片段:

struct B : public A { };
struct C : public B/*, public A*/ { };

从这里开始,真正的问题是:你需要多个A实例作为基础吗?

在评论中,@formerlyknownas-463035818给出了答案:这本质上是"钻石问题",可以通过虚拟继承来解决:

/* ... */
template<typename T>
struct B : public virtual A { // <---- Virtual inheritance
static const int global_val;
B() : A(global_val) {}
};
/* ... */
struct X : public B<X> { 
X() : A(B<X>::global_val) {} // Implementation classes should then initialize their A's individually
};
struct C : public X, public B<C> {
C() : A(B<C>::global_val) {} // Implementation classes should then initialize their A's individually
};
struct D : public X, public B<D> { 
D() : A(B<D>::global_val) {} // Implementation classes should then initialize their A's individually
};