Заставить все классы реализовать / переопределить «чисто виртуальный» метод в иерархии многоуровневого наследования - PullRequest
3 голосов
/ 28 февраля 2012

В C ++, почему метод pure virtual требует обязательного переопределения only для своих непосредственных потомков (для создания объектов), но не для внуков и так далее?

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 ++?

Ответы [ 5 ]

7 голосов
/ 12 марта 2012

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

Предположим, у нас есть несколько чистых 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,но по крайней мере это объявлено явно.

3 голосов
/ 28 февраля 2012

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

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

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

1 голос
/ 10 марта 2012

Есть ли практический способ получить этот эффект в C ++?

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

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

1 голос
/ 28 февраля 2012

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

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

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

1 голос
/ 28 февраля 2012

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

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

...