Подтвердить что ты не робот

С помощью С++, как правильно наследовать один и тот же базовый класс?

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

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. Это не будет проблемой, только я хотел бы использовать множественное наследование с любой комбинацией выше, чтобы я мог использовать функцию server/client на любой платформе, а также использовать функцию платформы на любом клиенте или сервере.

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

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

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

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

... yuck!

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

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

4b9b3361

Ответ 1

Это сработает, хотя вы получите две копии базового класса 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 }
}

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

Ответ 2

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

Ответ 3

Вы находитесь на С++, вы должны дружить с шаблонами. Используя шаблон template-argument-is-a-base-class, вам не понадобятся множественные наследования или избыточные реализации. Он будет выглядеть следующим образом:

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;

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

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;
  }
};

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

Ответ 7

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

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

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

Ответ 8

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

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

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