2013-04-16 3 views
64

Как реализовать конструктор копирования для класса, который имеет переменную-член unique_ptr? Я рассматриваю только C++ 11.Копировать конструктор для класса с unique_ptr

+6

Ну, что вы хотите, чтобы конструктор копирования выполнял? –

+0

Я читал, что unique_ptr непокрываем. Это заставляет меня задаться вопросом, как использовать класс, который имеет переменную-член unique_ptr в 'std :: vector'. – codefx

+0

Я думаю, что после копирования, если у нас есть 2 объекта с уникальными указателями, указывающими на одно и то же местоположение (объект), тогда это имеет смысл? Они должны быть уникальными. Если класс с указателем реализует конструктор копирования, то этот указатель должен быть общим указателем, я считаю не уникальным указателем. –

ответ

53

Поскольку unique_ptr не может использоваться совместно, вам необходимо либо глубоко скопировать его содержимое, либо преобразовать unique_ptr в shared_ptr.

class A 
{ 
    std::unique_ptr<int> up_; 

public: 
    A(int i) : up_(new int(i)) {} 
    A(const A& a) : up_(new int(*a.up_)) {} 
}; 

int main() 
{ 
    A a(42); 
    A b = a; 
} 

Вы можете, как уже упоминалось NPE, используйте Move-CTOR вместо копирования CTOR, но это привело бы к различной семантикой вашего класса. Движение-т е р должны были бы сделать член как движимое явно через std::move:

A(A&& a) : up_(std::move(a.up_)) {} 

Имея полный набор необходимых операторов также приводит к

A& operator=(const A& a) 
{ 
    up_.reset(new int(*a.up_)); 
    return *this, 
} 

A& operator=(A&& a) 
{ 
    up_ = std::move(a.up_); 
    return *this, 
} 

Если вы хотите использовать свой класс в std::vector, вам в основном нужно решить, должен ли вектор быть единственным владельцем объекта, и в этом случае было бы достаточно сделать класс подвижным, но не скопированным. Если вы не укажете copy-ctor и присваиваете копии, компилятор будет руководствоваться тем, как использовать std :: vector с типами перемещения.

+3

Можно ли упомянуть конструкторы перемещения? – NPE

+0

@NPE Да, отредактирован. Благодаря! –

+0

@ Даниэль, разве это не так, что для C++ 11 мне нужно следовать правилу четырех значений, реализующих как copy-ctor, так и move-ctor? – codefx

5

Daniel Frey упомянуть о решении копирования, я бы говорить о том, как переместить unique_ptr

#include <memory> 
class A 
{ 
    public: 
    A() : a_(new int(33)) {} 

    A(A &&data) : a_(std::move(data.a_)) 
    { 
    } 

    A& operator=(A &&data) 
    { 
     a_ = std::move(data.a_); 
     return *this; 
    } 

    private: 
    std::unique_ptr<int> a_; 
}; 

Они называются шаг конструктора и переместить назначение

вы могли бы использовать их, как это

int main() 
{ 
    A a; 
    A b(std::move(a)); //this will call move constructor, transfer the resource of a to b 

    A c; 
    a = std::move(c); //this will call move assignment, transfer the resource of c to a 

} 

Вам нужно обернуть a и c с помощью std :: move, потому что у них есть имя std :: move сообщает компилятору, чтобы преобразовать значение в ссылка Rvalue независимо от параметров В техническом смысле, станд :: ход аналогии с чем-то вроде «станд :: RValue»

После перемещения, то ресурс unique_ptr является переход к другой unique_ptr

Есть много тем, которые документируют ссылку rvalue; this is a pretty easy one to begin with.

Edit:

Двинутый объект shall remain valid but unspecified state.

C++ праймер 5, CH13 также дают очень хорошее объяснение о том, как «движение» объект

+1

так что происходит с объектом 'a' после вызова std :: move (a) в конструкторе перемещения' b'? Это просто совершенно недействительно? –

10

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

template< class T > 
    std::unique_ptr<T> copy_unique(const std::unique_ptr<T>& source) 
    { 
     return source ? std::make_unique<T>(*source) : nullptr; 
    } 

Например:

class My 
{ 
    My(const My& rhs) 
     : member(copy_unique(rhs.member)) 
    { 
    } 

    // ... other methods 

private: 
    std::unique_ptr<SomeType> member; 
}; 
+0

Будет ли она правильно копировать, если источник указывает на что-то, полученное от T? –

+0

@RomanShapovalov Нет, возможно, нет, вы бы получили нарезку. В этом случае решение, вероятно, будет заключаться в добавлении к вашему типу виртуального метода uniqueone_ptr clone() и обеспечения переопределения метода clone() в типах, полученных из T. Метод clone создаст новый экземпляр производного введите и верните это. –

+0

Нет ли уникальных указателей/облачных указателей в C++ или более мощных библиотеках с встроенными функциями глубокой копии? Было бы неплохо не создавать собственные конструкторы копирования и т. Д.для классов, которые используют эти интеллектуальные указатели, когда мы хотим, чтобы поведение с глубокой копией часто случалось. Просто интересуюсь. –

4

Обычный случай для одного, чтобы в классе с unique_ptr, чтобы иметь возможность использовать наследование (в противном случае простой объект будет делать, а, см RAII). Для этого случая нет соответствующего ответа в этой ветке до сих пор.

Итак, вот отправная точка:

struct Base 
{ 
    //some stuff 
}; 

struct Derived : public Base 
{ 
    //some stuff 
}; 

struct Foo 
{ 
    std::unique_ptr<Base> ptr; //points to Derived or some other derived class 
}; 

... и цель, как было сказано, чтобы сделать Foo воспроизводимую.

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

Это может быть достигнуто путем добавления следующего кода:

struct Base 
{ 
    //some stuff 

    auto clone() const { return std::unique_ptr<Base>(clone_impl()); } 
protected: 
    virtual Base* clone_impl() const = 0; 
}; 

struct Derived : public Base 
{ 
    //some stuff 

protected: 
    virtual Derived* clone_impl() const override { return new Derived(*this); };             
}; 

struct Foo 
{ 
    std::unique_ptr<Base> ptr; //points to Derived or some other derived class 

    //rule of five, but a user-defined dtor is not necessary due to unique_ptr 
    Foo(Foo const& other) : ptr(other.ptr->clone()) {} 
    Foo(Foo && other) = default; 
    Foo& operator=(Foo const& other) { ptr = other.ptr->clone(); return *this; } 
    Foo& operator=(Foo && other) = default; 
}; 

Там в основном две вещи, здесь происходит:

  • Первое добавление копирования и перемещения конструкторами, которые неявно удаляются в Foo, поскольку конструктор копирования unique_ptr удален. Конструктор перемещения может быть добавлен просто = default ..., который позволяет компилятору знать, что обычный конструктор перемещения должен удалить не (это работает, так как unique_ptr уже имеет конструктор перемещения, который может быть использован в этом случае) ,

    Для конструктора копирования Foo нет такого механизма, как нет конструктора копирования unique_ptr. Итак, нужно построить новый unique_ptr, заполнить его копией исходного указателя и использовать его как члена скопированного класса.

  • В случае, если наследование задействовано, копия оригинала должна быть выполнена тщательно. Причина в том, что выполнение простой копии с помощью std::unique_ptr<Base>(*ptr) в приведенном выше коде приведет к разрезанию, т. Е. Будет только скопирован только базовый компонент объекта, а производная часть отсутствует.

    Чтобы избежать этого, копия должна быть выполнена с помощью шаблона клонирования. Идея состоит в том, чтобы сделать копию через виртуальную функцию clone_impl(), которая возвращает Base* в базовом классе. В производном классе, однако, он расширяется с помощью ковариации, чтобы вернуть Derived*, и этот указатель указывает на вновь созданную копию производного класса. Базовый класс может затем получить доступ к этому новому объекту с помощью указателя базового класса Base*, обернуть его в unique_ptr и вернуть его через действительную функцию clone(), которая вызывается извне.

Смежные вопросы