'Inaccessible direct base' caused by multiple inheritance

前端 未结 2 1972
花落未央
花落未央 2020-11-29 06:51

Spoiler alert: Maybe a stupid question. :)

#include 

using namespace std;

class Base
{
    public:
        virtual void YourMethod(int) con         


        
相关标签:
2条回答
  • 2020-11-29 07:07

    This has nothing to do with overriding functions. It has to do with conversions. It really doesn't have to do with accessibility (i.e "private" or such) directly either. Here is a simpler example

    struct A { int a; };
    struct B : A { };
    struct C : B, A { }; // direct A can't be referred to!
    

    You can refer to the indirect A object by first converting to B and then to A:

    B *b = &somec;
    A *a = b;
    

    You cannot do such with the direct A object. If you try to directly convert to A, it will have two possibilities. It follows that it is impossible to refer to the non-static data members of the direct A object given a Derived object.

    Notice that accessibility is orthogonal to visibility. Something can be accessible even tho it's not visible (for example by refering to it by a qualified name), and something can be visible even though it's not accessible. Even if all the above derivations would be declared private, the problem would still show up: Access is checked last - it won't influence name lookup or conversion rules.

    Also, anyone can cast to an unambiguous private base class with defined behavior (the C++ Standard makes an exception for this) using a C-style cast, even if normally access wouldn't be granted to do so. And then there are still friends and the class itself that could freely convert.

    0 讨论(0)
  • 2020-11-29 07:16

    Johannes' answer covers the basic facts. But there's a little more to it. So, consider

    struct Base
    {
        Base( int ) {}
        void foo() const {}
    };
    
    struct Intermediate: Base
    {
        Intermediate( int x )
            : Base( x )
        {}
    };
    
    struct Derived: Intermediate, Base
    {
        Derived( int x )
            : Intermediate( x )
            , Base( x )         // OK
        {}
    };
    
    int main()
    {
        Derived o( 667 );
        o.foo();                // !Oops, ambiguous.
        o.Base::foo();          // !Oops, still ambiguous.
    }
    

    When I compile I get, as by now (after Johannes' answer) you'll expect,

    C:\test> gnuc x.cpp
    x.cpp:15: warning: direct base 'Base' inaccessible in 'Derived' due to ambiguity
    x.cpp: In function 'int main()':
    x.cpp:25: error: request for member 'foo' is ambiguous
    x.cpp:4: error: candidates are: void Base::foo() const
    x.cpp:4: error:                 void Base::foo() const
    x.cpp:26: error: 'Base' is an ambiguous base of 'Derived'
    
    C:\test> msvc x.cpp
    x.cpp
    x.cpp(15) : warning C4584: 'Derived' : base-class 'Base' is already a base-class of 'Intermediate'
            x.cpp(2) : see declaration of 'Base'
            x.cpp(7) : see declaration of 'Intermediate'
    x.cpp(25) : error C2385: ambiguous access of 'foo'
            could be the 'foo' in base 'Base'
            or could be the 'foo' in base 'Base'
    x.cpp(25) : error C3861: 'foo': identifier not found
    
    C:\test> _
    

    How to resolve depends on whether it's all right with a single sub-object of class Base (as is the case when Base is a pure interface), or Intermediate really requires its own Base sub-object.

    The latter case, two Base sub-objects, is probably not what you want, but if you want that then then one cure is to introduce yet another intermediate class, say, ResolvableBase.

    Like:

    struct Base
    {
        Base( int ) {}
        void foo() const {}
    };
    
    struct Intermediate: Base
    {
        Intermediate( int x )
            : Base( x )
        {}
    };
    
    struct ResolvableBase: Base
    {
        ResolvableBase( int x ): Base( x ) {}
    };
    
    struct Derived: Intermediate, ResolvableBase
    {
        Derived( int x )
            : Intermediate( x )
            , ResolvableBase( x )
        {}
    };
    
    int main()
    {
        Derived o( 667 );
        o.ResolvableBase::foo();    // OK.
    }
    

    In the first case, where e.g. Base is an interface and only one Base sub-object is needed, you can use virtual inheritance.

    Virtual inheritance generally adds some runtime overhead, and Visual C++ is not too fond of it.

    But it lets you "inherit in" an implementation of an interface, like in Java and C#:

    struct Base
    {
        Base( int ) {}
        virtual void foo() const = 0;
    };
    
    struct Intermediate: virtual Base
    {
        Intermediate( int x )
            : Base( x )
        {}
        void foo() const {}     // An implementation of Base::foo
    };
    
    struct Derived: virtual Base, Intermediate
    {
        Derived( int x )
            : Base( x )
            , Intermediate( x )
        {}
    };
    
    int main()
    {
        Derived o( 667 );
        o.foo();    // OK.
    }
    

    Subtlety: I changed the inheritance list order in order to avoid g++ sillywarnings about initialization order.

    Annoyance: Visual C++ issues sillywarning C4250 about inheritance (of implementation) via dominance. It's like "warning: you're using a standard main function". Oh well, just turn it off.

    Cheers & hth.,

    0 讨论(0)
提交回复
热议问题