多重继承和访问不明确的元素
Multiple inheritance and accessing ambiguous elements
我试图通过多重继承和模板化来实现一些运行时类型信息技巧,但遇到了一个模糊性问题。
首先,我所说的一个非常基本的例子:
#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表似乎只想指向我的D
和C
类的X
基。如何确保D
和C
实例的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
};
- Visual C++(VS2017)中用户定义的转换不明确
- 更新的矢量元素不打印
- 重载类方法的不明确调用
- 为函数定义符号不明确的指针参数
- 父类的私有函数会导致对具有相同名称和相似参数的子类中的公共函数的不明确调用
- 在 C++17 中的命名空间和子命名空间中重载运算符是不明确的
- C++ 编译器错误:P1LinkedList.cpp:145:错误:重载的"to_string(int&)"调用不明确
- 对重载函数find_first_not_of的不明确调用
- 不明确的成员模板查找
- 转置矩阵:交换元素不会更改值
- gcc出现不明确的模板实例化错误
- 调用'Node'构造函数是不明确的
- 如何解决不明确的运算符过载问题?
- 使用 nullptr 调用重载方法是不明确的
- "fpclassify":对重载函数的不明确调用
- 对"列表"的引用不明确,包括头文件
- 删除全局隐式函数 - 避免使用不明确的运算符
- 第 k 个最小元素 - 不能创建大于 k 大小的数组
- 当接收到不明确的规范时,表示图的邻接列表的数据结构
- 多重继承和访问不明确的元素