Подтвердить что ты не робот

Какая лучшая подпись для 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, если это необходимо.

Является ли один из этих подходов предпочтительным? Или у меня есть способ съесть мою передачу семантики собственности и у меня тоже сильная безопасность?

4b9b3361

Ответ 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, очевидно, будет реализован в терминах клона и инкапсулирует листинг.

Таким образом, вы можете получить самый производный умный указатель, который вы знаете во время компиляции. Это может быть не самый производный тип в целом, но он использует всю информацию, доступную компилятору.

Другим вариантом было бы создание версии шаблона клона, которая принимает тип, который вы ожидаете, но это увеличивает нагрузку на вызывающего.