Используя C++, как я правильно наследовался тому же базовому классу дважды?

Это - наша идеальная иерархия наследования:

class Foobar;

class FoobarClient : Foobar;

class FoobarServer : Foobar;

class WindowsFoobar : Foobar;

class UnixFoobar : Foobar;

class WindowsFoobarClient : WindowsFoobar, FoobarClient;

class WindowsFoobarServer : WindowsFoobar, FoobarServer;

class UnixFoobarClient : UnixFoobar, FoobarClient;

class UnixFoobarServer : UnixFoobar, FoobarServer;

Это вызвано тем, что наша иерархия наследования попыталась бы наследоваться Foobar дважды, и как таковой, компилятор жаловался бы на противоречивые ссылки на любых членах Foobar.

Позвольте мне объяснять, почему я хочу такую сложную модель. Это вызвано тем, что мы хотим иметь ту же переменную, доступную от WindowsFoobar, UnixFoobar, FoobarClient, и FoobarServer. Это не было бы проблемой, только я хотел бы использовать множественное наследование с любой комбинацией вышеупомянутого, так, чтобы я мог использовать функцию сервера/клиента на любой платформе и также использовать функцию платформы или на клиенте или на сервере.

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

Обновление 1:

Кроме того, полагайте, что мы могли использовать #ifdef для обхождения этого, однако, это будет иметь тенденцию приводить к очень ужасному коду как такой:

CFoobar::CFoobar()
#if SYSAPI_WIN32
: m_someData(1234)
#endif
{
}

... фу!

Обновление 2:

Для тех, кто хочет читать больше в фон этой проблемы, я действительно предлагаю скользить по соответствующему потоку списка рассылки. Вещь начинает становиться интересной вокруг 3-го сообщения. Также существует связанная фиксация кода, с которой Вы видите реальный рассматриваемый код здесь.

12
задан Nick Bolton 3 January 2010 в 14:24
поделиться

8 ответов

Это будет работать, хотя вы получите две копии базового класса Foobar . Чтобы получить единственную копию, вам нужно будет использовать виртуальное наследование. Читайте о множественном наследовании здесь .

class Foobar;

class FoobarClient : virtual public Foobar;

class FoobarServer : virtual public Foobar;

class WindowsFoobar : virtual public Foobar;

class UnixFoobar : virtual public Foobar;

Однако есть много проблем, связанных с множественным наследованием. Если вы действительно хотите, чтобы модель была представлена, почему бы не сделать так, чтобы FoobarClient и FoobarServer использовали ссылку на Foobar во время создания, а затем имели Foobar & FoobarClient / Server :: getFoobar ?

Композиция часто является выходом из множественного наследования. Возьмем теперь пример:

class WindowsFoobarClient : public WindowsFoobar 
{
    FoobarClient client;
public:
    WindowsFoobarClient() : client( this ) {}
    FoobarClient& getClient() { return client }
}

Однако следует проявлять осторожность при использовании этого в конструкторе.

18
ответ дан 2 December 2019 в 03:54
поделиться

Непосредственно за этим стоит функция виртуального наследования на C++. То, что вы здесь делаете - это кошмар с обслуживанием. Возможно, это не будет большим сюрпризом, так как известные авторы, такие как H. Sutter, уже давно выступают против такого использования наследования. Но это происходит из непосредственного опыта работы с таким кодом. Избегайте глубоких цепочек наследования. Очень бойтесь ключевого слова protected - его использование очень ограничено. Подобный дизайн быстро выходит из-под контроля - отследить паттерны доступа к защищенной переменной где-то вверх по цепочке наследования из классов более низкого уровня становится сложно, обязанности частей кода становятся расплывчатыми и т.д., а люди, которые год спустя будут смотреть на ваш код, будут вас ненавидеть :)

.
7
ответ дан 2 December 2019 в 03:54
поделиться

Вы находитесь в C++, вы должны быть дружелюбны к шаблонам. Используя шаблон-аргумент-в-основном-классе, Вам не понадобится многократное наследование или избыточная реализация. Все будет выглядеть следующим образом:

class Foobar {};

template <typename Base> class UnixFoobarAspect : public Base {};
template <typename Base> class WindowsFoobarAspect : public Base {};
template <typename Base> class FoobarClientAspect : public Base {};
template <typename Base> class FoobarServerAspect : public Base {};

typedef UnixFoobarAspect<FoobarClientAspect<Foobar>/*this whitespace not needed in C++0x*/> UnixFoobarClient;
typedef WindowsFoobarAspect<FoobarClientAspect<Foobar> > WindowsFoobarClient;
typedef UnixFoobarAspect<FoobarServerAspect<Foobar> > UnixFoobarServer;
typedef WindowsFoobarAspect<FoobarServerAspect<Foobar> > WindowsFoobarServer;

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

.
5
ответ дан 2 December 2019 в 03:54
поделиться

Посмотрите на этот поиск . Наследование Diamond - это в некотором роде претенциозный вопрос, и правильное решение зависит от конкретной ситуации.

Хотелось бы прокомментировать Unix/Windows сторону вещей. Обычно выходят #ifndef вещи, которые не подходят для конкретной платформы. Таким образом, вы получите только Foobar, скомпилированный для Windows или Unix с использованием директив препроцессора, а не UnixFoobar и WindowsFoobar. Посмотрите, как далеко вы можете зайти в использовании этой парадигмы, прежде чем исследовать виртуальное наследование.

2
ответ дан 2 December 2019 в 03:54
поделиться

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

Однако, это один из плохих случаев использования множественного наследования, который может доставить вам много трудностей. Скорее всего, вы не хотите, чтобы все было именно так.

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

.
0
ответ дан 2 December 2019 в 03:54
поделиться

Попробуйте этот пример состава и наследования:

class Client_Base;
class Server_Base;

class Foobar
{
  Client_Base * p_client;
  Server_Base * p_server;
};

class Windows_Client : public Client_Base;
class Windows_Server : public Server_Base;

class Win32 : Foobar
{
  Win32()
  {
    p_client = new Windows_Client;
    p_server = new Windows_Server;
  }
};

class Unix_Client : public Client_Base;
class Unix_Server : public Server_Base;

class Unix : Foobar
{
  Unix()
  {
    p_client = new Unix_Client;
    p_server = new Unix_Server;
  }
};

Многие эксперты говорили, что вопросы могут быть решены с помощью другого уровня бездействия.

2
ответ дан 2 December 2019 в 03:54
поделиться

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

Рассмотрим композицию вместо наследования.

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

1
ответ дан 2 December 2019 в 03:54
поделиться

Используйте виртуальное наследование, в объявлении FoobarClient , FoobarServer , WindowsFoobar и UnixFoobar , поместите слово виртуальный перед именем базового класса Foobar .

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

3
ответ дан 2 December 2019 в 03:54
поделиться
Другие вопросы по тегам:

Похожие вопросы: