C++ Multiple Inheritance + Virtual Functions (- Ambiguity) = Weird Behavior (also function pointers)

StackOverflow https://stackoverflow.com/questions/9106288

質問

I'm creating a couple of interfaces intended to provide access to a callback functionality. That is, inheriting from interface A allows a class to use callbacks of type one; interface B allows type two. Inheriting from both A and B allows callbacks of both types. The ultimate purpose is that classes A and B will take care of all the dirty work by just inheriting from them.

First Problem

Here's a small example that should illustrate some of the trouble I'm having:

class A
{
public:
    static void AFoo( void* inst )
    {
        ((A*)inst)->ABar( );
    }
    virtual void ABar( void ) = 0;
};

class B
{
public:
    static void BFoo( void* inst )
    {
        ((B*)inst)->BBar( );
    }
    virtual void BBar( void ) = 0;
};

class C : public A, public B
{
public:
    void ABar( void ){ cout << "A"; };
    void BBar( void ){ cout << "B"; };
};

And by making the calls

C* c_inst = new C( );
void (*AFoo) (void*) = C::AFoo;
void (*BFoo) (void*) = C::BFoo;
AFoo( (void*)c_inst );
BFoo( (void*)c_inst );

I expect I'll get "AB" as output. Instead I get "AA". Reversing the order of derived classes (B before A), produces "BB". Why is this?

Second Problem

The actual interfaces I'm using are templated, so the code appears more like

template <class T> class A
{
public:
    static void AFoo( void* inst )
    {
        ((T*)inst)->ABar( );
    }
    virtual void ABar( void ) = 0;
};

template <class T> class B
{
public:
    static void BFoo( void* inst )
    {
        ((T*)inst)->BBar( );
    }
    virtual void BBar( void ) = 0;
};

class C : public A<C>, public B<C>
{
public:
    void ABar( void ){ cout << "A"; };
    void BBar( void ){ cout << "B"; };
};

The reason for this is so A and B can do all the work, but their implementations don't need to have any knowledge of C.

Now, calling with

C* c_inst = new C( );
void (*AFoo) (void*) = C::AFoo;
void (*BFoo) (void*) = C::BFoo;
AFoo( (void*)c_inst );
BFoo( (void*)c_inst );

produces the correct output: "AB".

This small example works fine here, but it doesn't always work correctly in practice. Very strange things start happening, similar to the weirdness in the first problem above. The main issue appears to be that both virtual functions (or the static functions, or something) don't always make it into C.

For instance, I can successfully call C::AFoo(), but not always C::BFoo(). And this is sometimes dependent on the order in which I derive from A and B: class C: public A<C>, public B<C> may produce code where neither AFoo or BFoo work, while class C: public B<C>, public A<C> may produce code where one of them works, or maybe both.

Since the classes are templated, I can remove the virtual functions in A and B. Doing so produces working code, so long as ABar and BBar exist in C of course. That's acceptable, but not desired; I'd rather know what the problem is.

What possible reasons are there that the above code could cause bizarre problems?

Why does the second example produce the correct output where though the first doesn't?

役に立ちましたか?

解決

You're invoking undefined behavior. You can cast an X* to a void*, but once you've done that, the only thing it is safe to cast that void* is to an X* (this is not entirely true, I'm oversimplifying but for the sake of argument pretend it is).

Now why is the code behaving like it is? One way to implement MI is something akin to:

 struct A
 {
    A_vtable* vtbl;
 };

 struct B
 {
    B_vtable* vtbl;
 };

 struct C
 {
    struct A;
    struct B;
 };

In this example A is first, but the order will be determined by the compiler. When you cast to void, you get a pointer to the beginning of C. When you cast that void* back, you've lost the information you need to adjust the pointer appropriately if necessary. Since both A and B have a single virtual function with the same signature, you end up calling the impl. of whichever class happens to be first in the object layout.

他のヒント

As Logan Capaldo told, this approach of callback implementation has problems. Casting void* to XXX* is not safe, because we could not gurantee that the casted pointer actually points to XXX(it may point to other type or even invalid address, and it will result in unpredicted problems). My suggestion is changing the arguement type of your static function to the interface type, that is:

class A
{
public:
    static void AFoo( A* inst )
    {
        inst->ABar( );
    }
    virtual void ABar( void ) = 0;
};

class B
{
public:
    static void BFoo( B* inst )
    {
         inst->BBar( );
    }
    virtual void BBar( void ) = 0;
};

class C : public A, public B
{
public:
    void ABar( void ){ cout << "A"; };
    void BBar( void ){ cout << "B"; };
};


C* c_inst = new C( );
void (*AFoo) (void*) = C::AFoo;
void (*BFoo) (void*) = C::BFoo;
AFoo(c_inst );
BFoo(c_inst );

These are the benifits: First no void* cast is needed. Second, it force the users to pass the correct pointer type. And the users knows the exact argument type of those static functions without any documentation.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top