Какая лучшая подпись для clone () в C ++? - PullRequest
23 голосов
/ 03 ноября 2008

Как писал Скотт Майерс, вы можете воспользоваться расслаблением в системе типов 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;
};

К сожалению, ослабление соглашений не относится к шаблонным интеллектуальным указателям, и компилятор не разрешит переопределение.

Итак, мне кажется, у меня есть два варианта:

  1. Пусть clone () вернет "тупой" указатель и документ, который клиенты несут ответственность за его удаление.
  2. Пусть clone () возвращает умный базовый указатель, а клиенты используют dynamic_cast, чтобы сохранить их в производном указателе, если он им нужен.

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

Ответы [ 8 ]

26 голосов
/ 04 ноября 2008

Использование общедоступного не виртуального / частного виртуального шаблона:

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); }
};
19 голосов
/ 04 ноября 2008

Синтаксис не так хорош, но если вы добавите это в свой код выше, разве это не решит все ваши проблемы?

template <typename T>
std::auto_ptr<T> clone(T const* t)
{
    return t->clone();
}
7 голосов
/ 04 ноября 2008

Я думаю, что семантика функций настолько ясна в этом случае, что остается мало места для путаницы. Поэтому я думаю, что вы можете использовать ковариантную версию (ту, которая возвращает тупой указатель на реальный тип) с чистой совестью, и ваши посетители будут знать, что они получают новый объект, свойство которого передается им.

5 голосов
/ 04 ноября 2008

Это зависит от вашего варианта использования. Если вы когда-либо думаете, что вам нужно будет вызвать clone для производного объекта, динамический тип которого вы знаете (помните, что смысл clone в том, чтобы разрешить копирование без , зная динамический тип), тогда вы вероятно, следует вернуть тупой указатель и загрузить его в умный указатель в вызывающем коде. Если нет, то вам нужно только вернуть smart_ptr, и вы можете свободно возвращать его во всех переопределениях.

2 голосов
/ 04 ноября 2008

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, будут отброшены в версии выпуска.

1 голос
/ 02 августа 2016

Обновление 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);
    }
}
1 голос
/ 04 ноября 2008

Это одна из причин использования boost::intrusive_ptr вместо shared_ptr или auto/unique_ptr. Необработанный указатель содержит счетчик ссылок и может использоваться более плавно в подобных ситуациях.

0 голосов
/ 04 ноября 2008

У вас может быть два метода: виртуальный clone (), который возвращает умную оболочку указателя вокруг базового типа, и не виртуальный clone2 (), который возвращает правильный тип умного указателя.

clone2, очевидно, будет реализован в терминах клона и инкапсулирует приведение.

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

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

...