简体   繁体   中英

Virtual call from base in virtual inheritance

I'm having a problem making aa virtual call when using virtual inheritance.

Below is sample compilable code that demonstrates a code which works when there is no virtual inheritance used, and also code which will fail on runtime when virtual inheritance is used.

BASE CLASSES Here are base calsses for both cases:

#include <iostream>

class Base
{
public:
    Base() { }
    virtual ~Base() { }

    // we need to make this bad call a good one!
    virtual void bad_call(void* ptr)
    {
        Base* pThis = static_cast<Base*>(ptr);
        pThis->f();
    }

protected:
    virtual void f() { std::cout << x << std::endl; }
    int x = 0;
};

class Midle1 :
    virtual public Base
{
public:
    Midle1() { }
    ~Midle1() override { }
};

class Midle2 :
    virtual public Base
{
public:
    Midle2() { }
    ~Midle2() override { }
};

CASE 1 GOOD Here is a case which makes no use of virtual inheritance (just normal inheritance), where both bad_call and good_call virtual functions work:

class GoodDerived :
    public Base
{
public:
    GoodDerived()
    {
    }
    ~GoodDerived() override
    {
    }
    void good_call(void* ptr)
    {
        GoodDerived* pThis = static_cast<GoodDerived*>(ptr);
        pThis->f();
    }

    void f() override
    {
        ++x;
        std::cout << x << std::endl;
    }
};

int main()
{
    GoodDerived good_derived;
    good_derived.good_call(&good_derived);  // OK, will print 1
    good_derived.bad_call(&good_derived);   // OK, will print 2

    std::cin.get();
    return 0;
}

CASE 2 BAD And here is a case which will make use of virtual inheritance, the good_call function will succeed, but bad_call one will fail with "access violation reading location"

class BadDerived :
    public Midle1,
    public Midle2
{
public:
    BadDerived() { }
    ~BadDerived() override { }
    void good_call(void* ptr)
    {
        BadDerived* pThis = static_cast<BadDerived*>(ptr);
        pThis->f();
    }

    void f() override
    {
        ++x;
        std::cout << x << std::endl;
    }
};

int main()
{
    BadDerived bad_derived;
    bad_derived.good_call(&bad_derived); // OK, will print 1
    bad_derived.bad_call(&bad_derived); // ERROR: causes access violation

    std::cin.get();
    return 0;
}

QUESTION This second case is a simple code that demonstrated the issue I'm having right now in my project, and I need assistance on how to solve this, why is virtual inheritance causing troubles?

Why first case works just fine but second one does not?

The basic problem is that you're casting a pointer to void * and then casting it to a different pointer type. That doesn't work in general -- after casting a pointer to void * , the only useful thing you can do with it is cast it back to the EXACT SAME POINTER TYPE. If you want to cast to any other pointer type (reliably) you need to first cast back to the same original type.

I need assistance on how to solve this

Don't use void * here -- void * is a C solution that should never be used in C++. Change your virtual bad_call method to take a Base * as an argument not a void * . Then everything 'just works' and you don't need any of the static_cast s at all. If you need to override bad_call in your Dervied class, it also needs to take a Base * argument, so you'll need to use dynamic_cast<Derived *>(ptr) there to get back the original Derived * , but that's not a big deal -- that's precisely what dynamic_cast exists for.

Make your call:

bad_derived.bad_call(static_cast<Base*>(&bad_derived));

You want to point to the Base part of the object but when using virtual inheritance there is no guarantee about where that will be located.

Let's decompose this, step by step.

  1. &bad_derived : A pointer to BadDerived with pointer value pointing to an object with type BadDervived .
  2. bad_derived.bad_call(&bad_derived) : Implicitly converts &bad_derived to a pointer to void* with pointer value pointing to an object with type BadDervived .
  3. Base* pThis = static_cast<Base*>(ptr); : Cast from a void* to Base* . Note that ptr has pointer value pointing to an object with type BadDervived , but BadDerived is not pointer-interconvertable with Base , thus pThis has type Base* but has pointer value pointing to an object with type BadDervived .
  4. pThis->f(); : Access the value of a BadDerived object using a glvalue (here a dereferenced pointer) of type Base , violates the strict-aliasing-rule. Undefined Behaviour.

I want to share a solution that makes this design possible (with the help of other answers and comments).

All the code remains same except adding a templated static mehtod to base class which will deduce void to correct type:

Here is modified Base class with helper template static function: added a comment about CALLBACK also.

class Base
{
public:
    Base() { }
    virtual ~Base() { }

    // this is example CALLBACK from Window API but templated
    // The callback is registered with Windows in RegisterClassEx btw.
    template<typename DERIVED_CLASS>
    static void make_call(void* ptr)
    {
        DERIVED_CLASS* pThis = static_cast<DERIVED_CLASS*>(ptr);
        pThis->bad_call(static_cast<Base*>(pThis));
    }

    // we need to make this bad call a good one!
    virtual void bad_call(void* ptr)
    {
        Base* pThis = static_cast<Base*>(ptr);
        pThis->f();
    }

protected:
    virtual void f() { std::cout << x << std::endl; }
    int x = 0;
};

And here is how we invoke bad_call problematic function:

int main()
{
    BadDerived bad_derived;
    bad_derived.good_call(&bad_derived); // OK, will print 1

    // HACA!!!
    bad_derived.make_call<BadDerived>(&bad_derived);    // OK will print 2

    std::cin.get();
    return 0;
}

This why I like C++ so much, everything is possible...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM