C ++多接口继承和static_cast

时间:2017-11-16 14:10:01

标签: c++ inheritance casting multiple-inheritance

在以下代码中:

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;
}

如果我更改了TextView的继承顺序,我会在itv->setText();来电时遇到段错误。

为什么重要?我可以在这里使用static_cast,或者我在这里使用UB?据我所知dynamic_cast只需要虚拟继承,而且正如我所知道的那样,不是那种情况。

3 个答案:

答案 0 :(得分:5)

目前,您隐式地从TextView*转换为void*,然后明确地从void*转换为ITextView*。从/向void*进行转换时,这些转换不会执行任何指针调整,因此最终会得到ITextView*类型的指针,该指针实际上指向TextView(而不是ITextView subobject!):未定义的行为随之而来。

解决方案是在void*的两个侧面使用完全相同的类型来处理始终

TextView tv;

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

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

答案 1 :(得分:4)

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

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

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

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

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

有虚拟继承将其转换为“钻石”:wikipedia

答案 2 :(得分:0)

关键是你正在对static_cast传递一个void*来传递一个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而没有让编译器知道它是AB那么编译器不是能够指向正确的 B型部分,它位于 A型切片之后。只有知道首先是 qry1=spark.sql("SELECT * FROM (SELECT col1 as clf1, col2, count(col2) AS value_count FROM table1 GROUP BY col2,col1 order by value_count desc) a where value_count !=1") 才能知道这一点。