7

Я сделал тестовый код следующим образом:Неоднозначность множественного наследования интерфейсов в C++

#include <iostream> 
using namespace std; 

#ifndef interface 
#define interface struct 
#endif 

interface Base 
{ 
    virtual void funcBase() = 0; 
}; 

interface Derived1 : public Base 
{ 
    virtual void funcDerived1() = 0; 
}; 

interface Derived2 : public Base 
{ 
    virtual void funcDerived2() = 0; 
}; 

interface DDerived : public Derived1, public Derived2 
{ 
    virtual void funcDDerived() = 0; 
}; 

class Implementation : public DDerived 
{ 
public: 
    void funcBase() { cout << "base" << endl; } 
    void funcDerived1() { cout << "derived1" << endl; } 
    void funcDerived2() { cout << "derived2" << endl; } 
    void funcDDerived() { cout << "dderived" << endl; } 
}; 

int main() 
{ 
    DDerived *pObject = new Implementation; 
    pObject->funcBase(); 

    return 0; 
} 

причиной, почему я написал этот код, чтобы проверить, если функцию funcBase() может быть вызвана в экземпляре DDerived или не. Мой C++ complier (Visual Studio 2010) дал мне сообщение об ошибке компиляции, когда я попытался скомпилировать этот код. На мой взгляд, в этом коде нет проблем, потому что несомненно, что функция funcBase() будет реализована (таким образом, переопределена) в каком-то производном классе интерфейса DDerived, потому что она является чистой виртуальной. Другими словами, любая переменная указателя типа Implementation * должна быть связана с экземпляром класса, производящим Implentation и переопределяющим функцию funcBase().

Вопрос в том, почему компилятор дал мне такое сообщение об ошибке? Почему синтаксис C++ определен таким образом; то есть рассматривать этот случай как ошибку? Как я могу сделать запуск кода? Я хочу разрешить множественное наследование интерфейсов. Конечно, если я использую «виртуальную общественность» или повторно объявить функцию funcBase() в Implementation как

interface DDerived : public Derived1, public Derived2 
{ 
    virtual void funcBase() = 0; 
    virtual void funcDDerived() = 0; 
}; 

тогда все работает без проблем.

Но я не хочу этого делать и ищу более удобный метод, поскольку виртуальное наследование может ухудшить производительность, а повторное объявление так утомительно, если отношения наследования классов очень сложны. Существуют ли какие-либо методы для включения множественного наследования интерфейсов в C++, кроме использования виртуального наследования?

+0

Мои психические силы говорят мне, что ошибка вызывает неоднозначную функцию базового класса. –

+0

Но это не базовый класс, это либо базовый интерфейс, либо базовая структура. –

+0

Возможный дубликат [C++ Multiple Inheritance - почему вы не работаете?] (Http://stackoverflow.com/questions/5864466/c-multiple-inheritance-why-you-no-work) –

ответ

3

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

Виртуальное наследование - это решение на C++ для наследования одной и той же функции из нескольких баз, поэтому я бы предложил просто использовать этот подход.

Возможно, вы считали, что просто не наследуете одну и ту же функцию от нескольких баз? У вас действительно есть производный класс, который нужно уметь обрабатывать как Derived1 или Derived2 ИЛИ Base в зависимости от контекста?

В этом случае разработка конкретной проблемы, а не надуманного примера может помочь обеспечить лучший дизайн.

+1

Позвольте мне не согласиться. В большинстве случаев, когда OP публикует настоящую программу, люди начинают кричать, что вместо этого они должны публиковать SSCCE. Это один из тех редких случаев, когда OP делает, а затем вы жалуетесь на _that._ –

+0

@Mr Lister. Вы правы! К сожалению, в этом случае у меня недостаточно контекста (достаточно полный упрощенный код), чтобы сказать, почему «Base», «Derived» и «Derived2» - это не все ортогональные классы, непосредственно унаследованные в «DDerived». –

1
DDerived *pObject = new Implementation; 
pObject->funcBase(); 

Это создает указатель типа DDerived для реализации. Когда вы используете DDerived, у вас действительно есть указатель на интерфейс.

DDerived не знает о реализации funcBase из-за двусмысленности определения funcBase как в Derived1, так и в Derived2.

Это создало алмаз наследования, что и является причиной проблемы.

http://en.wikipedia.org/wiki/Diamond_problem

Я также должен был проверить на интерфейсе «ключевое слово» у вас есть там

это МС-специальное расширение, который распознается визуально студии

7

Как вы определили, Ваша структура объекта выглядит следующим образом:

enter image description here

важным моментом здесь заключается в том, что каждый экземпляр Implementation содержит два полностью отдельных экземпляра Base. Вы предоставляете опцию Base::funcBase, но она не знает, пытаетесь ли вы переопределить funcBase для Base, унаследованного через Derived1, или Base, унаследованного через Derived2.

Да, чистый способ борьбы с этим является виртуальным наследованием. Это изменит структуру таким образом, есть только один экземпляр базы:

enter image description here

Это почти несомненно, что вы действительно хотите. Да, он получил репутацию проблем с производительностью в дни примитивных компиляторов и 25 МГц 486 и т. Д. С современным компилятором и процессором у вас маловероятно, чтобы столкнуться с проблемой.

Другая возможность - это своего рода альтернатива на основе шаблонов, но это, как правило, пронизывает остальную часть вашего кода - то есть вместо того, чтобы передавать Base *, вы пишете шаблон, который будет работать со всем, что предоставляет функции A, B и C, затем передать (эквивалент) Implementation в качестве параметра шаблона.

+0

Спасибо за ответ. Вы сказали, что «но он не знает, пытаетесь ли вы переопределить funcBase для базы, которую вы унаследовали через Derived1 или Base, которую вы унаследовали через Derived2», но я думаю, что это не так; когда я заменил «DDerived * pObject» на «Реализация * pObject», компилятор больше не жаловался ... – user1361349

+0

Это может быть связано с тем, что две копии funcBase фактически переопределены тем же методом, что и в Реализации. Точно так же, как я сказал в своем вопросе, когда я добавил «void funcBase() = 0» в определении DDerived, ошибки компиляции не было. Что ты об этом думаешь? Спасибо. – user1361349

1

Я думаю, что C++ Standard 10.1.4 - 10.1.5 поможет вам понять проблему в коде.

class L { public: int next; /∗ ... ∗/ }; 
class A : public L { /∗...∗/ }; 
class B : public L { /∗...∗/ }; 
class C : public A, public B { void f(); /∗ ... ∗/ }; 

10.1.4 Базовый класс спецификатор, который не содержат ключевое слово виртуальный, указывает невиртуальный базовый класс. Спецификатор базового класса, который содержит , содержит ключевое слово virtual, указывает виртуальный базовый класс. Для каждого различного возникновения не виртуального базового класса в решетке класса самого производного класса наиболее производный объект (1.8) должен содержать соответствующий отдельный подобъект базового класса этого типа. Для каждого отдельного базового класса , который указан виртуальным, наиболее производный объект должен содержать один подобъект базового класса этого типа. [Пример: для объекта класса C, каждое отдельное вхождение класса L4 класса (не виртуального) L в решетке класса C соответствует взаимно однозначному с отдельным L-подобъектом в объекте тип C. Учитывая класс C, определенный выше, объект класса класса C будет иметь два объекта класса L, как показано ниже.

10.1.5 В таких решетках явная квалификация может использоваться для указания того, к какому объекту относится . Тело функции C :: f может ссылаться на член , следующий из каждого L-подобъекта: void C :: f() {A :: next = B :: next; } // хорошо образованный. Без A :: или B :: классификаторов, определение C :: е выше будет плохо формируется из-за неоднозначности

Так просто добавить классификаторов при вызове pObject-> funcBase() или решить двусмысленность по-другому.

pObject->Derived1::funcBase(); 

Обновлено: Также очень полезно чтение будет 10.3 Виртуальные функции Стандарта.

Желаем приятного уик-энд :)

Смежные вопросы