在菱形继承结构中,有没有办法在分支之间进行转换?

In a diamond inheritance structure, is there a way to cast between the branches?

我的代码中有一个菱形继承结构,其中有一个指向 底部对象。我试图将其指向两个菱形边左侧的指针,再次将其投射到菱形的顶部,然后再次投射到右侧。但显然,C++ 有点记住转换的顺序,但事情并不像预期的那样工作。示例代码:

#include <iostream>

class A
{
};

class B1 : public A
{
public:
    virtual int Return1() = 0;
};

class B2 : public A
{
public:
    virtual int Return2() = 0;
};

class C : public B1, public B2
{
public:
    virtual int Return1() { return 1; }
    virtual int Return2() { return 2; }
};

int main()
{
    C c;
    B1* b1 = &c;
    A* a = b1;
    B2* b2 = (B2*)a;
    std::cout << "Return2() = " << b2->Return2();
}

结果是Return2() = 1,显然这种方法是错误的。我知道类似这样的东西在 C# 中有效,所以我的问题是:C++ 中有没有办法做我在这里尝试的事情,或者 - 如果没有 - 为什么这不是一个选项?

您可以通过将最后一个转换更改为:

来看到所需的结果
B2* b2 = (B2*)&c;

这是一个与向上转型和向下转型相关的问题。

这里的问题是您正在使用 C 类型转换 (T) expr,这在 99% 的情况下在 C++ 中是个坏主意。

C 转换仅存在于 C++ 中,因为需要与 C 追溯兼容,并且可能以意想不到的方式运行。

来自 here:

When the C-style cast expression is encountered, the compiler attempts to interpret it as the following cast expressions, in this order:

a) const_cast<new_type>(expression);

b) static_cast<new_type>(expression), with extensions: pointer or reference to a derived class is additionally allowed to be cast to pointer or reference to unambiguous base class (and vice versa) even if the base class is inaccessible (that is, this cast ignores the private inheritance specifier). Same applies to casting pointer to member to pointer to member of unambiguous non-virtual base;

c) static_cast (with extensions) followed by const_cast;

d) reinterpret_cast<new_type>(expression);

e) reinterpret_cast followed by const_cast.

The first choice that satisfies the requirements of the respective cast operator is selected, even if it cannot be compiled (see example)

在 C++ 中向下转型时正确的转型类型是 dynamic_cast<T>(expr),它会在执行之前检查表达式的对象是否可以转型为派生类型 T。如果你这样做,你会得到一个编译时或运行时错误,而不是得到一个错误的行为。

C 风格的转换从不执行动态转换,因此 B2* b2 = (B2*)a 中的 (B2*) 等同于 reinterpret_cast<B2*>,这是一种盲目地将任何指针类型强制转换为任何其他类型的转换。通过这种方式,C++ 无法执行任何所需的指针“魔术”,通常需要将 C* 转换为有效的 B2*。

鉴于 C++ 中的多态性是通过 虚拟调度 使用方法表实现的,并且 b2 中的指针未指向正确的基 class(假设它实际上是指向 B1 的指针),您正在通过 b2.

访问 B1 而不是 B2 的 vtable

Return1Return2 都是它们各自抽象 classes 的 vtables 中的第一个函数,所以在你的情况下 Return1 被错误地称为 - 你 可以 在大多数实现中大体上近似于类似 b2->vtable[0]() 的虚拟调用。鉴于这两种方法都没有触及 this,没有任何中断并且函数 returns 不会使程序崩溃(这不能保证,因为这整个事情是未定义的行为)。

  • 由于继承不是虚拟的(对于 A),您有“Y”继承(2 A),
A     A
|     |
B1    B2
 \   /
   C

不是钻石 (1 A)。

  • 避免可能导致 reinterpret_cast 的 C 转换,并且大多数 reinterpret_cast 用法会导致未定义行为 (UB)。

  • 你可能会在你的情况下使用 dynamic_cast 来获得预期的行为(A 需要多态的,默认的虚拟析构函数完成这项工作):

class A
{
public:
    virtual ~A() = default; // Added to allow dynamic_cast
};

class B1 : public A
{
public:
    virtual int Return1() = 0;
};

class B2 : public A
{
public:
    virtual int Return2() = 0;
};

class C : public B1, public B2
{
public:
    // override used for extra check from compiler.
    int Return1() override { return 1; }
    int Return2() override { return 2; }
};

int main()
{
    C c;
    B1* b1 = &c;
    A* a = b1;
    B2* b2 = dynamic_cast<B2*>(a); // C-cast replaced by dynamic_cast
    assert(b2 != nullptr);
    std::cout << "Return2() = " << b2->Return2();
}

Demo