Основным ограничением ковариантных возвращаемых типов, реализованных в C ++, является то, что они работают только с необработанными указателями и ссылками.Нет никаких реальных причин , а не , чтобы использовать их, когда это возможно, но ограничение означает, что мы не можем всегда использовать их, когда они нам нужны.
Это легко преодолетьограничение при обеспечении идентичного пользовательского опыта, даже не полагаясь на языковые функции.Вот как.
Давайте перепишем наши классы, используя общий и популярный не виртуальный интерфейс идиома.
struct AbstractFactory
{
Base *create() {
return create_impl();
}
private:
virtual Base* create_impl() = 0;
};
struct ConcreteFactory : public AbstractFactory
{
Derived *create() {
return create_impl();
}
private:
Derived *create_impl() override {
return new Derived;
}
};
Теперь здесь происходит нечто интересное.create
больше не является виртуальным и, следовательно, может иметь любой тип возврата.Это не ограничено ковариантным правилом возвращаемых типов.create_impl
все еще ограничен, но он закрыт, никто не называет его, кроме самого класса, поэтому мы можем легко манипулировать им и полностью удалить ковариацию.
struct ConcreteFactory : public AbstractFactory
{
Derived *create() {
return create_impl();
}
private:
Base *create_impl() override {
return create_impl_derived();
}
virtual Derived *create_impl_derived() {
return new Derived;
}
};
Теперь и AbstractFactory
, и ConcreteFactory
имеет точно такой же интерфейс, как и раньше, без видимого ковариантного типа возврата.Что это значит для нас?Это означает, что мы можем свободно использовать интеллектуальные указатели.
// replace `sptr` with your favourite kind of smart pointer
struct AbstractFactory
{
sptr<Base> create() {
return create_impl();
}
private:
virtual sptr<Base> create_impl() = 0;
};
struct ConcreteFactory : public AbstractFactory
{
sptr<Derived> create() {
return create_impl();
}
private:
sptr<Base> create_impl() override {
return create_impl_derived();
}
virtual sptr<Derived> create_impl_derived() {
return make_smart<Derived>();
}
};
Здесь мы преодолели ограничение языка и предоставили эквивалент ковариантных типов возврата для наших классов, не полагаясь на ограниченную языковую функцию.
Примечаниедля технически склонных.
sptr<Base> create_impl() override {
return create_impl_derived();
}
Эта здесь функция неявно преобразует ("upcasts") указатель Derived
в указатель Base
.Если мы используем ковариантные типы возвращаемых данных, как это предусмотрено языком, такое преобразование вставляется компилятором автоматически при необходимости.К сожалению, этот язык достаточно умен, чтобы делать это для необработанных указателей.Для всего остального мы должны сделать это сами.К счастью, это довольно легко, если немного многословно.
(В данном конкретном случае может быть приемлемым, просто возвращая указатель Base
повсюду. Я не обсуждаю это.Я предполагаю, что нам абсолютно необходимо что-то вроде ковариантных типов возврата.)