Search code examples
c++inheritancecastingmultiple-inheritance

C++ multiple interfaces inheritance and static_cast


In the following code:

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

If I change TextViews inheritance order I'll have segfault on itv->setText(); call.

Why is it important? Can I use static_cast here, or I have UB here? As I understand dynamic_cast only required with virtual inheritance, and this, as I can tell, not that case.


Solution

  • As it stands, you convert implicitly from TextView* to void*, then explicitly from void* to ITextView*. These conversions do not perform any pointer adjustment when casting from/to void*, so you end up with a pointer of type ITextView* which actually points to a TextView (not to its ITextView subobject!): undefined behaviour ensues.

    The solution is to take care of always using the exact same type on both "sides" of the void*:

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