Как заставить все производные классы реализовать виртуальный метод? - PullRequest
4 голосов
/ 03 сентября 2011

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

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

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

Контекст

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

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

Ответы [ 5 ]

3 голосов
/ 03 сентября 2011

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

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>. (Замените свою функцию orignial на простой шаблон функции, который обеспечивает это.) Это гарантирует, что любой Dep внутри мастера имеет правильно реализованную функцию clone.

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

3 голосов
/ 03 сентября 2011

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

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

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

2 голосов
/ 03 сентября 2011

Если вы управляете базовым классом 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.,

2 голосов
/ 03 сентября 2011

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()
}
0 голосов
/ 03 сентября 2011

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

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

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

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

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

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

...