Какая лучшая подпись для clone() в С++?
Как написал Скотт Майерс, вы можете воспользоваться расслаблением в С++-типе-системе, чтобы объявить 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() верните указатель "немой" и запишите, что клиенты несут ответственность за его удаление.
- Имейте clone() верните умный базовый указатель и попросите клиентов использовать dynamic_cast для сохранения их в указателе Derived, если это необходимо.
Является ли один из этих подходов предпочтительным? Или у меня есть способ съесть мою передачу семантики собственности и у меня тоже сильная безопасность?
Ответы
Ответ 1
Это зависит от вашего варианта использования. Если вы когда-нибудь подумали, что вам нужно будет вызвать clone
на производном объекте, динамический тип которого вы знаете (помните, что вся точка clone
- это разрешить копирование, не зная динамического типа), то вам, вероятно, следует вернуть немой указатель и загрузить его в интеллектуальный указатель в вызывающем коде. Если нет, тогда вам нужно вернуть smart_ptr, и вы можете свободно возвращать его во всех переопределениях.
Ответ 2
Использовать открытый виртуальный/частный виртуальный шаблон:
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); }
};
Ответ 3
Синтаксис не такой приятный, но если вы добавите это в свой код выше, не решает ли он все ваши проблемы?
template <typename T>
std::auto_ptr<T> clone(T const* t)
{
return t->clone();
}
Ответ 4
Я думаю, что семантика функции настолько ясна в этом случае, что для путаницы мало места. Поэтому я думаю, что вы можете использовать ковариантную версию (ту, которая возвращает немой указатель на реальный тип) с легкой совестью, и ваши абоненты будут знать, что они получают новый объект, чье свойство передано им.
Ответ 5
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);
}
Накладные расходы, добавленные утверждением, будут выброшены в версию выпуска.
Ответ 6
Это одна из причин использования boost::intrusive_ptr
вместо shared_ptr
или auto/unique_ptr
. Необработанный указатель содержит счетчик ссылок и может использоваться более легко в таких ситуациях.
Ответ 7
Обновление MSalters answer для С++ 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);
}
}
Ответ 8
У вас может быть два метода: виртуальный клон(), который возвращает оболочку интеллектуального указателя вокруг базового типа и не виртуальный clone2(), который возвращает правильный тип интеллектуального указателя.
clone2, очевидно, будет реализован в терминах клона и инкапсулирует листинг.
Таким образом, вы можете получить самый производный умный указатель, который вы знаете во время компиляции. Это может быть не самый производный тип в целом, но он использует всю информацию, доступную компилятору.
Другим вариантом было бы создание версии шаблона клона, которая принимает тип, который вы ожидаете, но это увеличивает нагрузку на вызывающего.