【问题标题】:C++ multiple interfaces inheritance and static_castC++ 多接口继承和 static_cast
【发布时间】:2018-04-30 02:51:45
【问题描述】:

在以下代码中:

Live

#include <iostream>
#include <thread>
#include <mutex>
#include <functional>

struct IView {
    virtual void setOnClick() = 0;
};
struct ITextView : IView {
    virtual void setText() = 0;
};
struct IButton : ITextView {
    virtual void setRadius() = 0;
};

struct View : IView {
    int i = 1;
    virtual void setOnClick() override {
        std::cout << "setting OnClick! i: " << i << std::endl;
    };
};

/// Works as is
/// But if make "TextView : View, ITextView" - have segfault on the run
struct TextView : ITextView, View {
    int j = 2;
    virtual void setText() override {
        std::cout << "setting text! i: " << i << " j: " << j << std::endl;
    };

    // forward IView
    virtual void setOnClick() override {
        View::setOnClick();
    }
};


int main() {
    TextView tv;

    void* ptr = &tv;    // I need to pass raw pointer, and then restore "interface" from it

    ITextView* itv = static_cast<ITextView*>(ptr);  // I don't need safety checks here
    itv->setOnClick();
    itv->setText();

    return 0;
}

如果我更改 TextViews 继承顺序,我将在 itv-&gt;setText(); 调用时出现段错误。

为什么重要?我可以在这里使用static_cast,还是在这里使用UB?据我了解dynamic_cast 仅需要虚拟继承,据我所知,这种情况并非如此。

【问题讨论】:

  • 试试ITextView* itv = &amp;tv; void* ptr = itv;

标签: c++ inheritance casting multiple-inheritance


【解决方案1】:

指向不同类“切片”的指针是不同的。

所以当你通过 void * 传输时,你需要

void * ptr = static_cast<ITextView *>(&tv);
...
ITextView* itv = static_cast<ITextView*>(ptr)

请注意,您的基类层次结构中有两个 IView“实例”:

TextView  <- ITextView <- ... <- IView
          \- View <- IView

有虚拟继承可以把它变成“钻石”:wikipedia

【讨论】:

  • 在 OP 的例子中,这是 UB 吗?我没有段错误,这表明
  • @Fureeish - 是的,它是 UB。不必为了任何或多或少的 UB 而崩溃。
  • 静态演员在每一行都做了不同的事情。
  • 关于虚拟继承。 1)我需要传递原始指针。不能从void* 执行dynamic_cast 2) 即使我将static_cast void* 设置为IView ,然后执行dynamic_cast,动态转换的成本也非常高。对吗?
  • @tower120: 1) 使用原始指针:没有必要使用dynamic_cast。只需在投射 T1 * -> void * -> T2* 时使 T1=T2。 2) 是的,dynamic_cast 至少在一般情况下可能代价高昂(通常是多重继承......) 3) 虚拟继承(或者说缺乏继承)是一个完全独立的问题:照原样,有两种方法可以转换为 @ 987654328@,这会有所不同,从 IViewView 的 static_cast 是模棱两可的,并且在 View 中不提供 setOnClick-Implementation 会产生令人费解的行为。
【解决方案2】:

就目前而言,您从TextView* 隐式转换为void*,然后从void* 显式转换为ITextView*。当从/到void* 进行转换时,这些转换不会执行任何指针调整,因此您最终会得到一个ITextView* 类型的指针,它实际上指向TextView(而不是它的ITextView 子对象!):未定义的行为随之而来。

解决方案是始终void*的“两侧”使用完全相同的类型:

TextView tv;

void* ptr = static_cast<ITextView*>(&tv); // Adjust, then convert to void*

ITextView* itv = static_cast<ITextView*>(ptr);

【讨论】:

    【解决方案3】:

    关键是您正在对通过 void* 传递的不同类型执行 static_cast,它将实际类型隐藏到编译器。

    我的意思是:

    TextView tv;
    ITextView* itv = static_cast<ITextView*>(&tv);
    

    这是安全的,您正在显式转换为父类型。

    TextView tv;
    void* ptr = &tv;
    ITextView* itv = static_cast<ITextView*>(ptr);
    

    这里编译器不知道ptr 的实际类型并将其视为ITextView*,因此它无法将指针调整到对象的正确子部分(值得注意的是,这可能看起来工作,因为ITextView 没有任何成员变量)。这与如何在底层实现继承有关,请考虑:

    ---------------------
    | VTable A | Type A |
    ---------------------
    
    ---------------------
    | VTable B | Type B |
    ---------------------
    
    -----------------------------------------
    | VTable AB | Type A | Type B | Type AB |
    -----------------------------------------
    

    假设这是内存布局

    class A { /* members */ };
    class B { /* members */ };
    class AB : public A, public B { /* members */ };
    

    现在很清楚,如果您拥有AB 的地址,并且您将其转换为B* 而没有让编译器知道它是AB,那么编译器将无法指向正确的Type B 部分,位于 Type A 切片之后。只有知道它首先是 AB 才能知道这一点。

    【讨论】:

      猜你喜欢
      • 2022-08-12
      • 1970-01-01
      • 2011-07-31
      • 2017-09-11
      • 1970-01-01
      • 1970-01-01
      • 2021-08-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多