如何为具有unique_ptr
成员变量的类实现复制构造函数?我只考虑C ++ 11。
答案 0 :(得分:66)
由于无法共享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-assignment,编译器将指导你如何使用只移动类型的std :: vector。
答案 1 :(得分:22)
在类中拥有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;
};
这里基本上有两件事:
第一个是添加了复制和移动构造函数,这些构造函数在Foo
中被隐式删除,因为unique_ptr
的复制构造函数被删除了。移动构造函数可以简单地添加= default
...这只是为了让编译器知道通常的移动构造函数应该不被删除(这有效,unique_ptr
已经有一个移动构造函数,可以在这种情况下使用)。
对于Foo
的复制构造函数,没有类似的机制,因为没有unique_ptr
的复制构造函数。因此,必须构造一个新的unique_ptr
,用原始指针的副本填充它,并将其用作复制类的成员。
如果涉及继承,则必须仔细完成原始指针的副本。原因是在上面的代码中通过std::unique_ptr<Base>(*ptr)
进行简单复制会导致切片,即只复制对象的基本组件,而派生的部分则丢失。
为避免这种情况,必须通过克隆模式完成复制。我们的想法是通过虚函数clone_impl()
进行复制,该函数在基类中返回Base*
。但是,在派生类中,它通过协方差扩展为返回Derived*
,并且此指针指向新创建的派生类副本。然后,基类可以通过基类指针Base*
访问这个新对象,将其包装到unique_ptr
中,并通过从外部调用的实际clone()
函数返回它。 / p>
答案 2 :(得分: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;
};
答案 3 :(得分: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
}
你需要用std :: move包装a和c,因为它们有一个名字 std :: move告诉编译器将值转换为 rvalue引用无论参数是什么 从技术意义上讲,std :: move类似于“std :: rvalue”
移动后,unique_ptr的资源将转移到另一个unique_ptr
有许多主题记录了右值参考; this is a pretty easy one to begin with
编辑:
移动的对象shall remain valid but unspecified state。
C ++ primer 5,ch13也给出了关于如何“移动”对象的非常好的解释
答案 4 :(得分:1)
我建议使用make_unique
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;
}
答案 5 :(得分:-1)
const Timer = process.binding('timer_wrap').Timer;
const timeLeft = timer => {
timer.timeLeft = function () {
if (this._called) return 0;
return timer._idleStart + timer._idleTimeout - Timer.now()
};
return timer;
}
const timer = timeLeft(setTimeout(_ => console.log('hi'), 1000));
setInterval(_ => console.log('Time left: ', timer.timeLeft()), 100);
不可复制,只能移动。
这将直接影响测试,在您的第二个示例中,该示例也只能移动而不能复制。
实际上,使用unique_ptr
可以避免重大错误。
例如,您的第一个代码的主要问题是指针从未删除,这确实非常糟糕。说,您可以通过以下方法解决此问题:
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
。