Множественное наследование от двух производных классов

Asked
Viewd57078

27

У меня есть абстрактный базовый класс, который действует как интерфейс.

У меня есть два «набора» производных классов, которые реализуют половину абстрактного класса. (один «набор» определяет абстрактные виртуальные методы, связанные с инициализацией, другой «набор» определяет методы, связанные с фактической «работой».)

Затем я создал производные классы, которые используют множественное наследование для создания полностью определенных классов (и сами ничего не добавляют).

Итак: (плохой псевдокод)

 class AbsBase {
  virtual void init() = 0;
  virtual void work() = 0;
}

class AbsInit : public AbsBase {
  void init() { do_this(); }
  // work() still abs
}

class AbsWork : public AbsBase {
  void work() { do_this(); }
  // init() still abs
}

class NotAbsTotal : public AbsInit, public AbsWork {
  // Nothing, both should be defined
}
 

Прежде всего, могу я это сделать? Могу ли я наследовать от двух классов, которые являются производными от одной и той же базы? (Надеюсь).

Вот и настоящая проблема (я немного солгал выше, чтобы упростить пример).

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

 class AbsBase {
public:
  void init() { init_impl(); }
  void work() { work_impl(); }

private:
  virtual void init_impl() = 0;
  virtual void work_impl() = 0;
}
 

Потому что общая идиома - сделать все виртуальные методы закрытыми.

К сожалению, теперь и AbsInit, и AbsWork наследуют эти методы, и поэтому NotAbsTotal наследует "по два каждого" (я понимаю, что могу убивать то, что на самом деле происходит во время компиляции).

В любом случае, g ++ жалуется на то, что: «запрос члена init () неоднозначен» при попытке использовать класс.

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

Итак: - Я ошибаюсь со своей реализацией? - Это ограничение идиомы делать виртуальные методы приватными? - Как мне провести рефакторинг моего кода, чтобы сделать то, что я хочу? (Предоставляет один общий интерфейс, но позволяет заменять реализации на «наборы» функций-членов)

Изменить:

Кажется, я не первый: http://en.wikipedia.org/wiki/Diamond_problem

Кажется, решение здесь - виртуальное наследование. Я слышал о виртуальном наследовании и раньше, но еще не стал думать о нем. Я все еще открыт для предложений.

5 ответов

35

Похоже, вы хотите выполнить виртуальное наследование. Другой вопрос, окажется ли это хорошей идеей, но вот как это сделать:

 
class AbsBase {...};
class AbsInit: public virtual AbsBase {...};
class AbsWork: public virtual AbsBase {...};
class NotAbsTotal: public AbsInit, public AbsWork {...};
 

По сути, невиртуальное множественное наследование по умолчанию будет включать копию каждого базового класса в производном классе и все их методы. Вот почему у вас есть две копии AbsBase - и причина неоднозначности использования вашего метода заключается в том, что оба набора методов загружены, поэтому C ++ не имеет возможности узнать, к какой копии получить доступ!

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

  • Почему ты так говоришь? Каковы последствия виртуального наследования, помимо сложности синтаксиса?

    mmocny31 октября 2008, 19:51
  • Преобразование базового указателя в производный класс обычно включает корректировку указателя с использованием динамической таблицы поиска. Это дополнительное косвенное обращение приводит к стоимости преобразования базового значения в производное. При вызове виртуального метода эти накладные расходы связаны с шумом вызова.

    Martin v. Löwis31 октября 2008, 23:32
1

Это можно сделать, хотя от этого больше всего дрожит.

Вам необходимо использовать «виртуальное наследование», синтаксис которого похож на

 class AbsInit: public virtual AbsBase {...};
class AbsWork: public virtual AbsBase {...};
class NotAbsTotal: public AbsInit, public AbsWork {...};
 

Затем вы должны указать, какую функцию вы хотите использовать:

 NotAbsTotal::work()
{
    AbsInit::work_impl();
}
 

(ОБНОВЛЕНО с правильным синтаксисом)

1

Вам необходимо объявить наследование как виртуальное:

 struct AbsBase {
          virtual void init() = 0;
          virtual void work() = 0;
};

struct AbsInit : virtual public AbsBase {
          void init() {  }
};

struct AbsWork : virtual public AbsBase {
          void work() { }
};

struct NotAbsTotal : virtual public AbsInit, virtual public AbsWork {
};

void f(NotAbsTotal *p)
{
        p->init();
}

NotAbsTotal x;
 
  • При преобразовании виртуальной базы в производный класс всегда необходимо использовать dynamic_cast <>, что, в свою очередь, требует, чтобы виртуальный метод был определен в виртуальной базе. Простое использование виртуальной базы не должно вызывать проблем - она ​​полиморфно предоставит доступ к производному классу.

    Martin v. Löwis31 октября 2008, 23:29
  • Это сработало даже с моими невиртуальными общедоступными методами. Однако мне нужно будет попытаться увидеть, что происходит при использовании указателя типа Base на объект типа Derived, чтобы убедиться, что виртуальное наследование работает как перегрузка виртуальной функции :)

    mmocny31 октября 2008, 20:21
0

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

Открытое наследование следует использовать только для моделирования отношений "isa", например собака - это животное, квадрат - это форма и т. д.

Взгляните на книгу Скотта Мейера "Эффективный C ++", где вы найдете отличное эссе о том, как следует интерпретировать различные аспекты объектно-ориентированного проектирования.

Изменить: я забыл сказать, что хотя предоставленные до сих пор ответы технически верны, я не думаю, что какой-либо из них решает проблемы того, что вы пытаетесь моделировать, и это суть вашей проблемы!

HTH

ура,

Роб