"Недостижимая прямая база", вызванная множественным наследованием
Предупреждение о спойлере: Может быть, глупый вопрос.:)
#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
. Итак, откуда возникает двусмысленность? В конструкторе?
Ответы
Ответ 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.,