如何从非多态虚基类进行向下转型?

5

当没有涉及到虚函数时,是否有办法将一个虚基类向下转换为派生类?以下是一些代码来演示我所说的:

struct Base1
{
  int data;
};

struct Base2
{
  char odd_size[9];
};

struct ViBase
{
  double value;
};


struct MostDerived : Base1, Base2, virtual ViBase
{
  bool ok;
};


void foo(ViBase &v)
{
  MostDerived &md = somehow_cast<MostDerived&>(v);  //but HOW?
  md.ok = true;
}


int main()
{
  MostDerived md;
  foo(md);
}

请注意,此代码仅用于演示。我的实际场景相当复杂,涉及模板参数和从一个类型转换到另一个类型,只知道第一个类型是第二个类型的基类;它可以是普通基类或虚基类,并且可能具有虚函数或非虚函数。(在底部看到简化的示例)。我可以使用类型特征检测多态情况和虚/非虚基类情况,并解决除了非多态虚基类之外的所有问题。这就是我询问的内容。
我真的想不出如何进行转换:
- 隐式转换是不行的;它们只能进行向上转换。 - static_cast 明确禁止从虚基类进行转换: 5.2.9/2 ... 并且 B 既不是 D 的虚基类,也不是 D 的虚基类的基类。... - dynamic_cast 也无法完成,因为向下转换需要多态类 5.2.7/6 否则,v 必须是多态类型(10.3)的指针或 glvalue。 10.3/1 ... 声明或继承虚函数的类称为多态类。 - reinterpret_cast 在这里根本不适用。
如果 MostDerived 至少有一个虚函数,则当然可以使用 dynamic_cast 解决此问题。但是,当没有虚函数时,是否有办法进行转换?
(注意:所有引用都来自 C++11 草案 N3485)
鉴于评论过多地关注上面的示例代码,这里是我真实情况的简要说明:
template <class T_MostDerived>
struct Bar
{
  template <class T_Base>
  void foo(T_Base &b, typename std::enable_if<std::is_base_of<T_Base, T_MostDerived>::value>::type * = nullptr)
  {
    T_MostDerived &md = somehow_cast<T_MostDerived>(b);
    do_stuff_with(md);
  }
};

也就是说,我知道T_BaseT_MostDerived的基类(并且我知道T_MostDerived实际上是最派生类型),但我对它们的其他信息一无所知;Bar是我的代码,是库的一部分,未知的客户端可以使用。我可以检测到它是非多态虚基类,但在这种情况下我无法进行强制转换。


为什么你不能使 ViBase 成为多态的?(例如,你可以将其析构函数设为虚函数) - isekaijin
3
如果ViBase不是多态的,为什么要将其用作虚基类? - Niall
移除 virtual 关键字。然后,将引用/指针重新解释为您现在处理的普通结构体,并愉快地进行操作... - Anonymous
1
只要 Base1Base2ViBase 无关,那么在这里使用 virtual 继承会产生什么影响? - πάντα ῥεῖ
1
我认为你可能运气不佳,http://stackoverflow.com/a/10524622/3747990。但坦率地说,我认为你应该能够做你想做的事情。考虑向委员会提出这个问题。 - Niall
显示剩余8条评论
2个回答

4

MostDerived&到其ViBase&存在隐式且明确的转换。 static_cast可以显式表示这种转换,并且也可以执行相反的转换。这是static_cast所做的转换类型。

正如OP所指出的,从虚基类进行static_cast是无效的。

下面的源代码说明了原因:

#include <iostream>
using namespace std;

struct B { virtual ~B(){} };
struct D: virtual B {};
struct E: virtual B {};
struct X: D, E {};

auto main() -> int
{
    X   x;
    B&  b = static_cast<E&>( x );

    // Can't do the following for the address adjustment that would work for
    // D sub-object won't work for E sub-object, yet declarations of D and E
    // are identical -- so the address adjustment can't be inferred from that.
    //
    //static_cast<D&>( b );

    // This is OK:
    dynamic_cast<D&>( b );
}

基本上,如此所示,你无法仅从 D(或 E)的声明中推断出地址调整。编译器也无法做到这一点。这也排除了 reinterpret_cast

是的(加1),但这更像是一种观察。我应该把答案理解为“没有办法”吗?也许您可以更明确地陈述一下? - Angew is no longer proud of SO
1
@Angew:既然我们已经用所有明确定义的方法从非多态虚基类中降低了效果,那么就没有这样的方法了。这并不意味着“没有办法”,但这意味着无论你做什么都会有一些代价,例如可移植性(对于正式 UB 代码)或复杂性和内存占用(对于动态注册所有“Base”对象),或效率(对于已知模式的扫描)。我认为需要关注的主要问题是,将非多态虚基类作为静态已知类型,表明在设计层面上未能保留正确的类型。就我看来。 - Cheers and hth. - Alf

2

这需要一个技巧。向下转换需要进行数学计算,因为多重继承可能会将基类放在派生类的任意位置。然而,如果您知道基类是虚拟继承的,则在派生类中应该只有一个实例。这意味着您可以创建一个转换函数:

struct MostDerived : Base1, Base2, virtual ViBase
{
  bool ok;
  template <typename T> static MostDerived * somehow_cast (T *v) {
    static MostDerived derived;
    static T &from = derived;
    static size_t delta
      = reinterpret_cast<char *>(&from) - reinterpret_cast<char *>(&derived);
    char *to = reinterpret_cast<char *>(v);
    return reinterpret_cast<MostDerived *>(to - delta);
  }
};

特殊的C++转换类型相比于此函数所提供的,其优势在于类型安全。此函数盲目地假设传入的ViBase具有适当的派生子类可进行强制转换,而这通常并不是情况。

1
这里的限制是必须知道 *v 对象的最派生对象确实是一个 MostDerived,而不是从 MostDerived 派生的类。 - Cheers and hth. - Alf
1
如果你想避免未定义的行为,你需要创建一个查找表,将ViBase映射到MostDerived - jxh
有没有办法避免对MostDerived的默认构造要求?查找表如何避免未定义行为?填充表格时仍需要调用UB吗? - celticminstrel
@celticminstrel C使用类似的计算方式来实现offsetof(),并且它使用NULL指针转换到包含(MostDerived)结构。我不太舒服展示这个,因为编译器允许在NULL上进行有趣的数学运算,但常规代码实际上不应该这样做。 - jxh
@celticminstrel 查找表将在实例化“MostDerived”时填充。因此,不会有任何未定义行为。因此,如果您有unordered_map<Base1 *,MostDerived *>,则它将从MostDerived构造函数中填充,如:map[this] = this; - jxh
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接