3

В C++, почему чистыйvirtual метод предписывает его обязательным наиважнейшая только его непосредственных детей (для создания объекта), но не внукам и так далее?Force все классы для реализации/переопределения «чистый виртуальный» метод в иерархии наследования многоуровневой

struct B { 
    virtual void foo() = 0; 
}; 
struct D : B { 
    virtual void foo() { ... }; 
}; 

struct DD : D { 
    // ok! ... if 'B::foo' is not overridden; it will use 'D::foo' implicitly 
}; 

Я не вижу больших проблем при выходе из этой функции.
Например, при формулировке дизайна, возможно, было возможно, что struct DD разрешено использовать D::foo, только если у него есть явный оператор, такой как using D::foo;. В противном случае он должен переопределить foo обязательным.

Есть ли какой-либо практический способ использования этого эффекта на C++?

+1

Вы можете указать бит 'foo' в отдельный класс и потребовать, чтобы каждый класс наследовал непосредственно от него (в частном порядке). –

+5

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

+0

@iammilind: Извините, я до сих пор не уверен, что вы имеете в виду. Нет ничего особенного в отношении непосредственных детей по сравнению с другими потомками. Может быть, пример того, почему вы думаете, что внуки разные. –

ответ

7

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

Предположим, у нас есть несколько чистых virtual методов в class B (базовый):

class B { 
    virtual void foo() = 0; 
    virtual void bar (int) = 0; 
}; 

Среди них, я хочу foo() быть переопределены всей иерархии, то абстрактном foo() 1 уровень вверх для простоты:

class Register_foo { 
    template<typename T> // this matches the signature of 'foo' 
    Register_foo (void (T::*)()) {} 
}; 
class Base : public virtual Register_foo { // <---- virtual inheritance 
    virtual void bar (int) = 0; 
    Base() : Register_foo(&Base::foo) {} // <--- explicitly pass the function name 
}; 

Каждый последующий ребенок класса в иерархии должны регистр ИТС foo внутри его каждый конструкторявно. например:

struct D : B { 
    D() : Register_foo(&D::foo) {} 
    D (const D &other) : Register_foo(&D::foo) {} 
    virtual void foo() {}; 
}; 

Этот механизм регистрации не имеет ничего общего с бизнес-логикой. Но, по крайней мере, внутри конструктора любого дочернего класса можно было бы упомянуть, что он использует foo.
Хотя ребенок class может зарегистрироваться, используя свой собственный foo или его родительский foo, но не менее объявил явно.

1

В вашем примере вы не объявили D::foo чистым; поэтому его не нужно переопределять. Если вы хотите потребовать, чтобы он был переопределен, тогда объявите его чистым.

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

1

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

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

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

+2

Реальный вопрос, почему вы хотели бы это сделать. Кажется, он налагает ненужные ограничения на ** реализацию ** производных классов, что нежелательно; ограничения должны касаться только пред- и пост-условий и инвариантов классов. –

3

Что вы в основном просите, так это потребовать, чтобы самый производный класс реализовал функционал. И мой вопрос: почему? О единственном времени я могу себе представить, что это актуальная функция, такая как clone() или another(), которая возвращает новый экземпляр того же типа. И это , что вы действительно хотите обеспечить, чтобы новый экземпляр имел тот же тип ; даже там, где фактически реализована функция, не имеет значения.И вы можете применять, что:

class Base 
{ 
    virtual Base* doClone() const = 0; 
public: 
    Base* clone() const 
    { 
     Base* results = doClone(); 
     assert(typeid(*results) == typeid(*this)); 
     return results; 
    } 
} 

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

+0

Проблема в том, что часть после последнего дочернего элемента, которая реализовала clone(), не будет клонировать() d после вызова clone() в указателе базового класса, поэтому некоторые данные будут пропущены молча. По крайней мере, эта проверка типов является хорошим решением. – Nuclear

+1

@Nuclear Единственная проблема заключается в том, что это время запуска, а не время компиляции. Лучше, чем ничего, я полагаю. Но, как я уже сказал, я никогда не видел, чтобы это было реальной проблемой на практике. –

1

Есть ли какой-либо практический способ использования этого эффекта на C++?

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

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

+0

Мой вопрос не о существующих проектах. Если вы пишете базовый класс, и вы хотите применить его к своей иерархии. – iammilind

+0

Затем, используя шаблон CRTP, вы можете это сделать. См. Мои изменения выше. – clanmjc

+0

@iammilind вы понимаете, что если вы только планируете писать код один раз и никогда не будете его модифицировать, это в какой-то момент будет «существующим проектом». – iheanyi