2009-07-28 3 views
8

Почему рекомендуется не иметь членов данных в виртуальном базовом классе?Элементы данных виртуального базового класса

Что относительно членов функции? Если у меня есть задача, общая для всех производных классов, нормально ли для виртуального базового класса выполнять задачу или должен ли производный наследовать от двух классов - от виртуального интерфейса и простой базы, выполняющей эту задачу?

Спасибо.

+6

Не могли бы вы предоставить ссылку или цитату для консультации? см. большую точку в маркировке базового класса virtual, если у него нет данных, поскольку наиболее распространенной целью виртуального наследования является предотвращение дублирования членов базового класса. Например, в стандартных библиотеках ios_base имеет члены данных и являетсявиртуальный базовый класс (через ios) как istream, так и ostream. Поэтому я почти (но не совсем) говорю наоборот: если у вас будет виртуальный базовый класс, тогда он должен иметь членов данных или наследовать практически. –

ответ

11

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

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

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

Редактировать Я сделал некоторые домашние работы после комментария Мартина, спасибо Марин. Первая строка это не совсем верно:

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

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

Non virtual virtual 
    A  A   A 
    |  |  / \ 
    B  C  B  C 
    \ /  \ /
    D    D 

В виртуальном случае B и C разделяют один и тот же экземпляр А.

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

+0

+1 Это также обобщается на C++ FAQ Lite http://www.new-brunswick.net/workshop/c++/faq/multiple-inheritance.html#faq-25.8 –

+0

Я не думаю, что это правда. –

+0

@Martin: к сожалению, это ... –

0

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

б) не декларируют вещи, которые вы в настоящее время не используют - объявлять переменные только в классах, которые доступ/использовать их

с) виртуальные базовые классы должны содержать только интерфейсы/виртуальные методы, ничего более

Я надеюсь, что помогает немного, даже если мои причины не идеальны и полны :)

чао, Крис

1

Я никогда не видел эту рекомендацию.

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

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

+1

Если вы можете получить копию «Эффективного C++», прочитайте пункт 20: «Избегайте использования данных в общедоступных интерфейсах». – quark

+0

Правильно, я согласен с этим. Я не предлагаю публиковать данные. В любом случае, я неправильно понял вопрос и думаю, что ответ Иэна хорош. –

2

Основной совет - иметь конструктор по умолчанию в виртуальной базе. Если вы этого не сделаете, то каждый наиболее производный класс (то есть. Любой подкласс) должен вызвать виртуальную базу CTOR явно, и это приводит к разгневанным коллегам стучатся в вашей двери офиса ...

class VirtualBase { 
public: 
    explicit VirtualBase(int i) : m_i(i) {} 
    virtual ~VirtualBase() {} 

private: 
    int m_i; 
}; 

class Derived : public virtual VirtualBase { 
public: 
    Derived() : VirtualBase(0) {} // ok, this is to be expected 
}; 

class DerivedDerived : public Derived { // no VirtualBase visible 
public: 
    DerivedDerived() : Derived() {} // ok? no: error: need to explicitly 
            // call VirtualBase::VirtualBase!! 
    DerivedDerived() : VirtualBase(0), Derived() {} // ok 
}; 
+0

Вы имеете в виду, если я не назвал виртуальный базовый ctor явно, он не будет вызываться при создании производного объекта? – jackhab

+0

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

0

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

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

  • сделать их как можно более скрытыми и использовать геттеры и сеттеры, если вам нужно сохранить инварианты классов.
    (Здесь есть лазейка: если нет никакого инварианта, связанного с членом, то есть он может принимать любое возможное значение в любой момент времени, вы можете сделать его общедоступным. Однако это отрицает производный класс, добавляющий инвариант.
  • Дизайн для наследования: контракт вашего класса должен определять обязанности и возможности производного класса, что ему нужно/может переписываться с какой целью.
Смежные вопросы