Подтвердить что ты не робот

"Недостижимая прямая база", вызванная множественным наследованием

Предупреждение о спойлере: Может быть, глупый вопрос.:)

#include <iostream>

using namespace std;

class Base
{
    public:
        virtual void YourMethod(int) const = 0;
};

class Intermediate : private Base
{
    public:
        virtual void YourMethod(int i) const
        {
            cout << "Calling from Intermediate" << i << "\n";
        }
};

class Derived : private Intermediate, public Base
{
    public:
        void YourMethod(int i) const
        {
            cout << "Calling from Derived : " << i << "\n";
        }
};

int main()
{
}

Может мне кто-то Объяснить, почему это выдает компилятор предупреждение:

main.cpp:21: warning: direct base ‘Base’ inaccessible in ‘Derived’ due to ambiguity

Теперь я понимаю, что этот код не будет работать. Я хочу знать, почему. Base является закрытым для Intermediate, поэтому он не должен быть видимым до Derived через Intermediate. Итак, откуда возникает двусмысленность? В конструкторе?

4b9b3361

Ответ 1

Это не имеет ничего общего с переопределяющими функциями. Это связано с преобразованиями. Это действительно не обязательно связано с доступностью (например, "private" или такой). Вот более простой пример

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

Вы можете ссылаться на косвенный объект A, сначала преобразовывая его в B, а затем в A:

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

Вы не можете сделать это с прямым объектом A. Если вы попытаетесь напрямую преобразовать в A, у него будет две возможности. Из этого следует, что нельзя ссылаться на нестатические элементы данных прямого объекта A, заданного объектом Derived.

Обратите внимание, что доступность ортогональна видимости. Что-то может быть доступно, даже если оно не видно (например, ссылаясь на него по квалифицированному имени), и что-то можно увидеть, даже если оно не доступно. Даже если все вышеприведенные определения будут объявлены private, проблема все равно будет отображаться: доступ проверяется последним - это не повлияет на правила поиска имени или преобразования.

Кроме того, любой может применить к однозначному частному базовому классу с определенным поведением (С++ Standard делает исключение для этого) с использованием каста C-стиля, даже если обычно доступ к ним не будет предоставлен. И тогда есть еще друзья и сам класс, который мог свободно конвертировать.

Ответ 2

Ответ Йоханнеса охватывает основные факты. Но там немного больше. Итак, рассмотрим

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

Когда я скомпилирую, я получаю, так как теперь (после ответа Йоханнеса) вы ожидаете,

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> _

Как разрешить, зависит от того, все ли это правильно с одним под-объектом класса Base (как в случае, когда Base является чистым интерфейсом), или Intermediate действительно требует своего собственного Base sub -объект.

Последний случай, два под-объекта Base, вероятно, не то, что вы хотите, но если вы этого хотите, тогда одно из способов - ввести еще один промежуточный класс, скажем, ResolvableBase.

Как

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

В первом случае, где, например, Base - это интерфейс, и требуется только один под-объект Base, вы можете использовать виртуальное наследование.

Виртуальное наследование обычно добавляет некоторые служебные данные во время выполнения, а Visual С++ не слишком любит его.

Но он позволяет вам "наследовать" реализацию интерфейса, например, в Java и С#:

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

Тонкость: я изменил порядок списков наследования, чтобы избежать глупостей g++ относительно порядка инициализации.

Раздражение: Visual С++ выдает глупости C4250 о наследовании (реализации) через доминирование. Это как "предупреждение: вы используете стандартную основную функцию". Ну, просто отключай.

Приветствия и hth.,