Так как ваш Derived
класс является классом Base
, он никогда не должен ужесточать предварительные условия базового контракта : если он должен вести себя как Base
, он следует принять BaseInput
все в порядке. Это известно как принцип замещения Лискова.
Несмотря на то, что вы можете выполнять проверку аргумента во время выполнения, вы никогда не сможете достичь полностью безопасного для типов способа сделать это: ваш компилятор может соответствовать DerivedInput
, когда он видит объект Derived
(статический тип) , но он не может знать, какой подтип будет за Base
объектом ...
Требования
DerivedX
должен занять DerivedXInput
DerivedX::Foo
должен быть равен интерфейсу DerivedY::Foo
противоречат: либо методы Foo
реализованы в терминах BaseInput
и, следовательно, имеют идентичные интерфейсы во всех производных классах, либо типы DerivedXInput
различаются, и они не могут иметь тот же интерфейс.
Это, на мой взгляд, проблема.
Эта проблема возникла и у меня, когда я писал тесно связанные классы, которые обрабатываются в не зависящей от типа среде:
class Fruit {};
class FruitTree {
virtual Fruit* pick() = 0;
};
class FruitEater {
virtual void eat( Fruit* ) = 0;
};
class Banana : public Fruit {};
class BananaTree {
virtual Banana* pick() { return new Banana; }
};
class BananaEater : public FruitEater {
void eat( Fruit* f ){
assert( dynamic_cast<Banana*>(f)!=0 );
delete f;
}
};
и рамки:
struct FruitPipeLine {
FruitTree* tree;
FruitEater* eater;
void cycle(){
eater->eat( tree->pick() );
}
};
Теперь это доказывает, что дизайн слишком легко сломать: в дизайне нет части, которая бы выравнивала деревья с едоками:
FruitPipeLine pipe = { new BananaTree, new LemonEater }; // compiles fine
pipe.cycle(); // crash, probably.
Вы можете улучшить согласованность проекта и устранить необходимость виртуальной диспетчеризации, сделав его шаблоном:
template<class F> class Tree {
F* pick(); // no implementation
};
template<class F> class Eater {
void eat( F* f ){ delete f; } // default implementation is possible
};
template<class F> PipeLine {
Tree<F> tree;
Eater<F> eater;
void cycle(){ eater.eat( tree.pick() ); }
};
Реализации действительно являются шаблонными специализациями:
template<> class Tree<Banana> {
Banana* pick(){ return new Banana; }
};
...
PipeLine<Banana> pipe; // can't be wrong
pipe.cycle(); // no typechecking needed.