Какая лучшая подпись для clone () в C ++?
-
06-07-2019 - |
Вопрос
Как писал Скотт Майерс, вы можете воспользоваться расслаблением в системе типов C ++ для объявления clone (), возвращающего указатель на фактический объявленный тип:
class Base
{
virtual Base* clone() const = 0;
};
class Derived : public Base
{
virtual Derived* clone() const
};
Компилятор обнаруживает, что clone () возвращает указатель на тип объекта, и позволяет Derived переопределить его, чтобы вернуть указатель на производный.
Было бы желательно, чтобы clone () возвращал умный указатель, который подразумевает передачу семантики владения, например:
class Base
{
virtual std::auto_ptr<Base> clone() const = 0;
};
class Derived : public Base
{
virtual std::auto_ptr<Derived> clone() const;
};
К сожалению, ослабление соглашений не применяется к шаблонным интеллектуальным указателям, и компилятор не разрешит переопределение.
Похоже, у меня есть два варианта:
<Ол>Является ли один из этих подходов предпочтительным? Или у меня есть способ съесть мою семантику передачи права собственности и иметь строгую безопасность типов?
Решение
Это зависит от вашего варианта использования. Если вы когда-либо думаете, что вам нужно будет вызвать clone
для производного объекта, динамический тип которого вы знаете (помните, что весь смысл <=> состоит в том, чтобы разрешить копирование без , зная динамический тип), тогда вы, вероятно, должны вернуть тупой указатель и загрузить его в умный указатель в вызывающем коде. Если нет, то вам нужно только вернуть smart_ptr, и вы можете свободно возвращать его во всех переопределениях.
Другие советы
Используйте общедоступный не виртуальный / частный виртуальный шаблон:
class Base {
public:
std::auto_ptr<Base> clone () { return doClone(); }
private:
virtual Base* doClone() { return new (*this); }
};
class Derived : public Base {
public:
std::auto_ptr<Derived> clone () { return doClone(); }
private:
virtual Derived* doClone() { return new (*this); }
};
Синтаксис не так хорош, но если вы добавите это в свой код выше, разве это не решит все ваши проблемы?
template <typename T>
std::auto_ptr<T> clone(T const* t)
{
return t->clone();
}
Я думаю, что семантика функций настолько ясна в этом случае, что остается мало места для путаницы. Поэтому я думаю, что вы можете использовать ковариантную версию (ту, которая возвращает тупой указатель на реальный тип) с чистой совестью, и ваши посетители будут знать, что они получают новый объект, свойство которого передается им.
Tr1::shared_ptr<>
может быть приведен как исходный указатель.
Я думаю, функция clone (), возвращающая указатель shared_ptr<Base>
, является довольно чистым решением. Вы можете привести указатель к shared_ptr<Derived>
с помощью tr1::static_pointer_cast<Derived>
или tr1::dynamic_pointer_cast<Derived>
, если невозможно определить тип клонированного объекта во время компиляции. р>
Чтобы гарантировать предсказуемость типа объекта, вы можете использовать полиморфное приведение для shared_ptr, например:
template <typename R, typename T>
inline std::tr1::shared_ptr<R> polymorphic_pointer_downcast(T &p)
{
assert( std::tr1::dynamic_pointer_cast<R>(p) );
return std::tr1::static_pointer_cast<R>(p);
}
Издержки, добавленные assert, будут отброшены в версии выпуска.
Это одна из причин использовать boost::intrusive_ptr
вместо shared_ptr
или auto/unique_ptr
. Необработанный указатель содержит счетчик ссылок и может использоваться более плавно в подобных ситуациях. Р>
Обновление ответа MSalters для C ++ 14:
#include <memory>
class Base
{
public:
std::unique_ptr<Base> clone() const
{
return do_clone();
}
private:
virtual std::unique_ptr<Base> do_clone() const
{
return std::make_unique<Base>(*this);
}
};
class Derived : public Base
{
private:
virtual std::unique_ptr<Base> do_clone() const override
{
return std::make_unique<Derived>(*this);
}
}
У вас может быть два метода: виртуальный clone (), который возвращает умную обертку указателя вокруг базового типа, и не виртуальный clone2 (), который возвращает правильный тип умного указателя. Р>
clone2, очевидно, будет реализован в терминах клона и инкапсулирует приведение.
Таким способом можно получить наиболее производный умный указатель, какой вы знаете во время компиляции. Это может быть не самый производный тип в целом, но он использует всю информацию, доступную компилятору.
Другой вариант - создать шаблонную версию клона, которая принимает ожидаемый тип, но это увеличивает нагрузку на вызывающего.