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

Спойлер: возможно глупый вопрос. :)

#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. Так откуда же двусмысленность? В конструкторе?


person nakiya    schedule 07.11.2010    source источник
comment
Это не ошибка компилятора, это просто предупреждение.   -  person Prasoon Saurav    schedule 07.11.2010
comment
Я полагаю, что вызов Derived->YourMethod(5) должен быть в порядке... в конце концов, и в Base, и в Intermediate YuorMethod является виртуальным, так почему бы вам не определить свою собственную имплиментацию. Вы никоим образом не вызываете базовые функции, поэтому публичные частные отношения не должны иметь значения?   -  person thecoshman    schedule 07.11.2010
comment
Интересно, что в VS2010 вместо этого я получаю это предупреждение компилятора... предупреждение C4584: «Производный»: базовый класс «Базовый» уже является базовым классом «Промежуточного»   -  person Kyle C    schedule 07.11.2010
comment
@ Кайл С: да, мой плохой. Я видел это как ошибку.   -  person nakiya    schedule 07.11.2010
comment
Одна из распространенных причин, по которой я обнаружил, почему происходит этот сценарий, заключается в наличии чисто виртуальных базовых классов, которые действуют как интерфейсы. Во-вторых, повторно заявляя о наследовании «Base» в производном классе 3-го/4-го поколения, вы подтверждаете, что производный класс должен следовать определенному шаблону, обеспечивающему ясность кода. Я рекомендую добавить «виртуальное» наследование класса типа интерфейса.   -  person J Jorgenson    schedule 13.12.2011


Ответы (2)


Это не имеет ничего общего с переопределяющими функциями. Это связано с конверсиями. На самом деле это не имеет прямого отношения к доступности (то есть «частной» или тому подобное). Вот более простой пример

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, проблема все равно обнаружилась бы: доступ проверяется в последнюю очередь - это не повлияет на поиск имени или правила преобразования.

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

person Johannes Schaub - litb    schedule 07.11.2010
comment
Как ссылка на что-то по полному имени может быть примером того, что что-то доступно, даже если оно невидимо? - person nakiya; 07.11.2010
comment
@nakiya class A { int a; void f() { int a; /* outer a is not visible */ } };, но вы можете обратиться к нему по A::a. После локального a в f член класса был доступен (потому что это тот же класс), но его имя было скрыто локальным a. - person Johannes Schaub - litb; 07.11.2010
comment
Удерживает ли ваша аналогия деревья наследования? Если я в частном порядке наследую B от A и публично наследую C от B, то A не должен быть виден C (защита сделает это возможным). Поэтому, если я также наследую C от A, C в идеале должен видеть только свои A, а не Bs A. Но, я согласен, это не так. - person nakiya; 07.11.2010
comment
@nakiya технически это может иметь смысл (я думаю, что в Java частные имена членов не наследуются, в отличие от C++), но правила C++ не таковы: доступность ни для чего не имеет значения - она ​​проверяется только после того, как все остальное учредил. Я не знаю, в чем причина этого - возможно, это сделано для простоты (поиск имени для друзей потенциально может быть полностью другим, чем для не друзей). - person Johannes Schaub - litb; 07.11.2010

Ответ Йоханнеса охватывает основные факты. Но есть еще кое-что. Итак, рассмотрим

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.

Последний случай, два подобъекта 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 C++ не слишком любит его.

Но он позволяет «наследовать» реализацию интерфейса, как в Java и 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.
}

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

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

Ура и чт.,

person Cheers and hth. - Alf    schedule 07.11.2010
comment
Моя проблема заключается в том, что вместо того, чтобы использовать Derived отдельно, когда вы используете его через дескриптор типа Intermediate (в моем вопросе), не должно быть проблем с разрешением, потому что Intermediate происходит частным образом от Base. - person nakiya; 07.11.2010
comment
@nakiya: если предположить, что под дескриптором вы подразумеваете ссылку, я не вижу этого в вашем вопросе, но тогда это не проблема. Однако компилятор может по-прежнему предупреждать об определении Derived. Но почему вы наследуете напрямую от Base в Derived? Ваше здоровье, - person Cheers and hth. - Alf; 07.11.2010
comment
Под дескриптором я подразумеваю ссылку или указатель. См. этот вопрос (и мой ответ) как ответ на ваш второй вопрос: stackoverflow.com/questions/4117538/ - person nakiya; 07.11.2010