15

У меня есть несколько сценарий наследования бриллиантом, как это:Множественное наследование + виртуальные функции беспорядок

A 
/ \ 
B  C 
    \ /
    D 

Общий родитель, A, определяет виртуальную функцию Fn().
Возможно ли для B и C определить fn()?
Если это так, то следующий вопрос - может ли D получить доступ как к B и C fn() без значения? Я предполагаю, что для этого есть некоторый синтаксис.
И возможно ли, чтобы это сделать, не зная конкретно, кто такие B и C? B и C могут заменяться некоторыми другими классами, и я хочу, чтобы код в D был общим.

Я пытаюсь сделать так, чтобы D как-то перечислил все экземпляры fn(), которые он имеет в своей родословной. Возможно ли это каким-то другим способом, что виртуальные функции?

ответ

1

Уже есть несколько вопросов, которые касаются этого. Похоже, у нас заканчиваются вопросы, чтобы спросить. Возможно, окно поиска должно быть больше, чем кнопка Ask Question.

См

+0

Я не мог найти вопрос, который отвечает на эту конкретную проблему. ты можешь? – shoosh

+0

Это не потому, что речь идет о множественном наследовании, которое можно догадаться, что оно уже было рассмотрено в других сообщениях. Он спросил: «Я пытаюсь сделать так, чтобы D как-то перечислил все экземпляры fn(), которые он имеет в своей родословной. Возможно ли это каким-то другим способом, что виртуальные функции? ».Хотя я думаю, что это был несколько наивный вопрос, ни один из вопросов, которые вы здесь связали, не говорит об этом. Я думаю, он был довольно конкретным и уникальным в своих опросах. -1. –

4

Первый вопрос, да, B и C можно определить fn() в качестве виртуальной функции. Во-вторых, D может, конечно, получить доступ к B::fn() и C::fn() с помощью оператора области: Третий вопрос: D должен хотя бы знать B и C, так как вы должны определить их в списке наследования. Вы можете использовать шаблоны, чтобы типы В и С открытым:

class A 
{ 
public: 
    virtual ~A() {} 
    virtual void fn() = 0; 
}; 

class B: public A 
{ 
public: 
    virtual ~B() {} 
    virtual void fn(){ std::cout << "B::fn()" << std::endl; } 
}; 

class C: public A 
{ 
public: 
    virtual ~C() {} 
    virtual void fn(){ std::cout << "C::fn()" << std::endl; } 
}; 

template <typename TypeB, typename TypeC> 
class D: public TypeB, public TypeC 
{ 
public: 
    void Do() 
    { 
     static_cast<TypeB*>(this)->fn(); 
     static_cast<TypeC*>(this)->fn(); 
    } 
}; 

typedef D<B, C> DInst; 

DInst d; 
d.Do(); 

О желании автоматически перечислять все функции п() всех классов, D наследует от: Я не уверен, если это возможно, без прибегая к MPL. По крайней мере, вы можете расширить мой пример выше версиями, которые имеют дело с 3 и более параметрами шаблона, но я предполагаю, что существует верхний (внутренний компилятор) предел количества параметров шаблона класса.

+0

Я собирался опубликовать то же самое. Есть небольшая ошибка, поскольку D должен наследовать как от B, так и от C, и это не указано в коде выше. Другой синтаксис (более простой, чем приведение) был бы следующим: TypeB :: fn() и TypeA :: fn(), которые выглядят более естественными. –

+0

Я не уверен, что вызов типаB :: fn() делает правильную вещь в отношении вызова виртуальной функции. Со статическим броском вы уверены, что у вас есть объект типа B. Я думаю, я должен попробовать это. Спасибо за примечание об исправлении! – vividos

+1

Это, вероятно, самое близкое решение для того, что мне нужно. К сожалению, в моем случае количество классов в наследовании (B, C) также является переменной. предположим, что придется ждать аргументов шаблона переменной C++ 0x. – shoosh

1

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

0

Vividos уже ответил на основную часть сообщения. Даже если бы я использовал оператор области видимости вместо более громоздкого static_cast <> + оператора разыменования.

В зависимости от задачи, возможно, вы можете изменить соотношение наследования от D до B и C для меньшей комбинации соединений (плюс, возможно, наследование от A). Это предполагает, что вам не нужно, чтобы D использовалось политически как B или C, и что вам действительно не требуется, чтобы B и C использовали один и тот же базовый экземпляр.

Если вы выберете композицию, вы можете получить аргументы B и C в качестве конструктора в качестве ссылок/указателей типа A, что делает D полностью не осведомленным о типах B и C. В этот момент вы можете использовать контейнер для хранения как можно большего числа объектов A. Ваша собственная реализация fn() (если вы так решите) или любой другой метод.

18

Если вы не перепишете fn еще раз в D, то это невозможно. Поскольку в объекте D нет конечного переопределения: оба C и B переопределяют A::fn. У вас есть несколько вариантов:

  • Капля C::fn или B::fn. Затем тот, который по-прежнему переопределяет A::fn, имеет окончательный повод.
  • Поместите конечный переулок в D. Затем этот символ переопределяет A::fn, а также fn в C и B.

Например следующие результаты в компиляции ошибки времени:

#include <iostream> 

class A { 
public: 
    virtual void fn() { } 
}; 

class B : public virtual A { 
public: 
    virtual void fn() { } 
}; 

class C : public virtual A { 
public: 
    virtual void fn() { } 
}; 

// does not override fn!! 
class D : public B, public C { 
public: 
    virtual void doit() { 
     B::fn(); 
     C::fn(); 
    } 
}; 

int main(int argc, char **argv) { 
    D d; 
    d.doit(); 
    return 0; 
} 

Вы можете, однако получить невиртуальном от А в С и В, но у вас нет алмазного наследства больше. То есть каждый элемент данных в A появляется дважды в B и C, потому что у вас есть два под-объекта базового класса в объекте D. Я бы рекомендовал вам пересмотреть этот дизайн. Попытайтесь устранить двойные объекты, подобные виртуальным наследованиям. Это часто вызывает такие конфликтующие ситуации.

Очень похожий случай, когда вы хотите переопределить определенную функцию. Представьте, что у вас есть виртуальная функция с тем же именем в B и C (теперь без общей базы A). А в D вы хотите переопределить каждую функцию, но каждый из них должен по-разному реагировать. В зависимости от того, вызываете ли вы функцию с помощью указателя B или указателя C, у вас другое поведение. Multiple Inheritance Part III Herb Sutter описывает хороший способ сделать это. Это может помочь вам принять решение о вашем дизайне.

+0

Я думаю, что это не то, что хочет сделать shoosh. Он заявил: «Я пытаюсь сделать так, чтобы D как-то перечислил все экземпляры fn(), которые он имеет в своей родословной». Он не хочет переопределять fn() в классе D. – vividos

+3

, поэтому я сказал, что это невозможно. если оба C и B переопределяют A :: fn, то он не может определить D без переопределения fn в D –

+0

Что вы имели в виду под «Drop»? – yanpas

1

Возможно, вы захотите ознакомиться с Loki TypeLists, если вам действительно нужно иметь возможность отслеживать родословную и перечислить типы. Я не уверен, что то, о чем вы просите, действительно возможно без кучи работы. Убедитесь, что вы здесь не слишком сложны.

В немного отличающемся примечании, если вы собираетесь использовать MI таким образом (то есть страшный бриллиант), тогда вы должны быть очень ясными о том, какой виртуальный член вы хотите. Я не думаю о хорошем случае, когда вы хотите выбрать семантику B::fn() за C::fn() без явного принятия решения при написании D. Вероятно, вы выбрали один из них (или даже оба), исходя из того, что делает индивидуальный метод. После того, как вы приняли решение, требование состоит в том, что унаследованные изменения не изменяют ожидания или семантический интерфейс.

Если вы действительно беспокоитесь об обмене в новом классе, скажем E вместо сказать B где E не происходит от B, но предлагает тот же интерфейс, то вы действительно должны использовать шаблон подход, хотя я не уверен, почему в там static_cast<> ...

struct A { 
    virtual ~A() {} 
    virtual void f() = 0; 
}; 
struct B: A { 
    virtual void f() { std::cout << "B::f()" << std::endl; } 
}; 
struct C: A { 
    virtual void f() { std::cout << "C::f()" << std::endl; } 
}; 

template <typename Base1, typename Base2> 
struct D: Base1, Base2 { 
    void g() { Base1::f(); Base2::f(); } 
}; 

int main() { 
    D<B,C> d1; 
    D<C,B> d2; 
    d1.g(); 
    d2.g(); 
    return 0; 
} 

// Outputs: 
// B::f() 
// C::f() 
// C::f() 
// B::f() 

работает отлично и, кажется, немного легче смотреть.

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