Как заставить все производные классы реализовать виртуальный метод?

Скажем, у вас есть базовый класс Dep для дерева классов. Есть виртуальный метод Dep* Dep::create() что я хочу быть реализованным каждым листовым классом. Есть ли способ обеспечить это?

Примечание: проблема здесь в том, что могут быть промежуточные классы (скажем, class B : public A : public Dep) реализация этого метода (A::create) случайно или потому что они думают, что они листовые классы, но на самом деле подклассы сами.

Здесь вопрос заканчивается.

контекст

Если вам интересно, зачем мне это нужно; У меня есть класс Master у которого есть Dep объекты неизвестного конкретного типа. Если Master дублируется, мне нужно придумать соответствующий клон Dep пример. Следующее, что лучше всего сделать, это идиома виртуального конструктора, которая представляет именно эту проблему.

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

5 ответов

Решение

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

template<typename T>
class Cloneable : public T, public Dep
{
private:
    Cloneable<T>() : T() { }
public:
    static Cloneable<T>* Create() { return new Cloneable<T>(); }
    Cloneable<T>* clone() { return new Cloneable<T>(*this); }
};

Вместо того, чтобы выводить из Dep и создание экземпляров через new MyTypeиспользовать Cloneable<MyType>::Create, поскольку Cloneable<MyType> происходит от MyType, вы можете использовать экземпляр так же, как вы использовали бы любой MyTypeза исключением того, что теперь гарантированно Dep::clone,

Кроме того, ваш Master не должен принимать экземпляр типа Dep, но убедись, что это Cloneable<T>, (Замените свою оригинальную функцию простым шаблоном функции, который обеспечивает это.) Это гарантирует, что любой Dep внутри мастер правильно реализован clone функция.

поскольку Cloneable<MyType> не имеет публичного конструктора, он не может быть унаследован, однако ваш фактический MyType в дальнейшем может наследоваться и использоваться так же, как и раньше.

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

Например, A может наследовать от Def и реализовать все это [чистые] виртуальные методы. Тогда если B наследуется от Aне нужно ничего реализовывать. Нет никакого способа предотвратить это.

Таким образом, ответ - нет, нет способа обеспечить это.

Если вы контролируете базовый класс AbstractDep тогда вы можете принудительно установить, что конкретные листовые классы должны быть созданы с помощью шаблона класса WithCloning, Затем этот лист можно запечатать, чтобы он не мог быть унаследован. Точнее, нельзя создавать экземпляры из производного класса.

class AbstractDep
{
template< class Type > friend class WithCloning;
private:
    enum FooFoo {};
    virtual FooFoo toBeImplementedByLeafClass() = 0;

public:
    virtual AbstractDep* clone() const = 0;
};

template< class Type > class WithCloning;

class Sealed
{
template< class Type > friend class WithCloning;
private:
    Sealed() {}
};

template< class Type >
class WithCloning
    : public Type
    , public virtual Sealed
{
private:
    AbstractDep::FooFoo toBeImplementedByLeafClass()
    {
        return AbstractDep::FooFoo();
    }

public:
    virtual WithCloning* clone() const
    {
        return new WithCloning( *this );
    }
};

typedef WithCloning<AbstractDep>            Dep;


class AbstractDerivedDep
    : public AbstractDep
{
// AbstractDep::FooFoo toBeImplementedByLeafClass(); // !Not compile.
public:
};

typedef WithCloning<AbstractDerivedDep>     DDep;


struct Foo: Dep {};       // !Does not compile if instantiated.

int main()
{
    Dep     d;
    //Foo     f;
}

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

Одно из решений - переслать пакет аргументов из WithCloning конструктор (в моем блоге есть пример реализации C++98, и C++0x поддерживает это напрямую).

Подводя итог, чтобы быть инстанцируемым, класс должен быть WithCloning,

Ура & hth.,

TPTB объявил вне закона все RTTI или только dynamic_cast<>()? Если вы можете использовать RTTI, тогда вы можете обеспечить существование метода в качестве постусловия его вызова:

#include <typeinfo>
#include <cassert>
#include <iostream>
#include <stdexcept>

class Base {
protected:
  virtual Base* do_create() = 0;
  virtual ~Base() {}
public:
  Base* create() {
    Base *that = this->do_create();
    if( typeid(*this) != typeid(*that) ) {
      throw(std::logic_error(std::string() +
                             "Type: " +
                             typeid(*this).name() +
                             " != " +
                             typeid(*that).name()));
    }
    return that;
  }
};

class Derive1 : public Base {
protected:
  Base* do_create() { return new Derive1(*this); }
};

class Derive2 : public Derive1 {};

void f(Base*p) { std::cout << typeid(*p).name() << "\n"; }
int main() {
  Derive1 d1;
  Base *pD1 = d1.create(); // will succeed with correct semantics
  Derive2 d2;
  Base *pD2 = d2.create(); // will throw exception due to missing Derive2::do_create()
}

Когда вы говорите, что они неизвестны, я предполагаю, что они все еще наследуют от общего базового класса / интерфейса, верно?

единственное, что я могу думать о вас, можно использовать для принудительного добавления виртуального базового класса

virtual Base* clone() {
    assert( 1==0 ); //needs to be overriden
}

поэтому вы вынуждены переопределить, но это обнаруживается только во время выполнения при попытке вызвать клон для экземпляра класса, в котором отсутствует переопределение

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

Похоже, вы знакомы с шаблоном клонирования, но я спокойно опубликую его здесь, и мы можем забыть об этом: http://www.cplusplus.com/forum/articles/18757/

Другие вопросы по тегам