对于一个具有 only_ptr 的类,可以使用复制建构子

如何实现一个包含 unique_ptr成员变量的类的复制建构子?我只考虑 C + + 11。

117583 次浏览

由于无法共享 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 而不是 copy-ctor,但是这将导致类的不同语义。一个 move-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 和 copy-sign,编译器将指导您如何使用带有仅移动类型的 std: : Vector。

关于 Daniel Frey 提到的复制解决方案,我想谈谈如何移动 only _ 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


}

您需要通过 std: : move 包装 a 和 c,因为它们有一个名称 Move 告诉编译器将值转换为 不管参数是什么,rvalue 引用 在技术意义上,std: : move 与“ std: : rvalue”类似

移动之后,惟一 _ ptr 的资源被传输到另一个惟一 _ ptr

有许多主题文档 rvalue 引用;。

编辑:

移动的对象 应保持有效但未指定的状态

C + + 入门程序5,ch13也给出了一个非常好的关于如何“移动”对象的解释

尝试使用这个助手来创建深度副本,并在源 only _ ptr 为 null 时进行处理。

    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;
};

在类中使用 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
~Foo() = default;
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;
};

这里基本上发生了两件事:

  • 首先是增加一个用户定义的复制建构子,这是必要的,因为 unique_ptr成员本身没有复制建构子。在声明的复制构造函数中,创建一个新的 unique_ptr,并将指针设置为原始指针的副本。

  • 如果涉及到继承,则必须仔细地执行原始指针的副本。原因是在上面的代码中通过 std::unique_ptr<Base>(*ptr)做一个简单的复制会导致切片,也就是说,只有对象的基本组件被复制,而派生的部分丢失。

    为了避免这种情况,复制必须通过克隆模式完成 想法是通过一个虚函数 clone_impl()进行复制 返回基类中的 Base*。在派生类中, 然而,它是通过协方差扩展的返回一个 Derived*,和 此指针指向派生类的一个新创建的副本 然后,基类可以通过基类指针访问这个新对象 Base*,将其包装成 unique_ptr,并通过实际的 从外部调用的 clone()函数。

  • 其次,通过像上面那样声明一个用户定义的复制构造函数,move 构造函数会被相应的 C + + 语言规则删除。因此,通过 Foo(Foo &&) = default的声明只是让编译器知道标准 move 构造函数仍然适用。

我建议使用 make _ only

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


public:
A( int i ) : up_(std::make_unique<int>(i)) {}
A( const A& a ) : up_(std::make_unique<int>(*a.up_)) {};


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

unique_ptr不是可复制的,它只是可移动的。

这将直接影响 Test,在您的第二个示例中,Test 也只是可移动的,而不是可复制的。

事实上,使用 unique_ptr是件好事,它可以保护您免受一个大错误的影响。

例如,第一个代码的主要问题是指针永远不会被删除,这是非常非常糟糕的。比如说,你可以通过:

class Test
{
int* ptr; // writing this in one line is meh, not sure if even standard C++


Test() : ptr(new int(10)) {}
~Test() {delete ptr;}
};


int main()
{
Test o;
Test t = o;
}

这也很糟糕。如果复制 Test会发生什么?将有两个类具有指向同一地址的指针。

当销毁一个 Test时,它也会销毁指针。当第二个 Test被销毁时,它也会尝试删除指针后面的内存。但是它已经被删除了,我们会得到一些错误的内存访问运行时错误(或者未定义行为,如果我们运气不好的话)。

因此,正确的方法是实现复制建构子和复制赋值操作符,这样行为就清楚了,我们可以创建一个副本。

unique_ptr在这方面领先我们很多。它具有语义上的意义: “ 我是 unique,所以你不能只是复制我。”,因此,它避免了我们现在实现手边操作符的错误。

您可以为特殊行为定义复制建构子和复制赋值操作符,代码就可以工作了。但你是,理所当然的被迫这么做。

这个故事的寓意是: 在这种情况下总是使用 unique_ptr