具有unique_ptr成员变量的 C++ 移动/复制/赋值<AbstractClass>

c++ move/copy/assignment with a unique_ptr<AbstractClass> member variable

本文关键字:复制 赋值 gt lt AbstractClass 移动 ptr unique 成员 变量 C++      更新时间:2023-10-16

我遇到了一个与非常相似的问题

如何使具有unique_ptr成员的类使用std::move和std::swap?

并且我正在使用VS2013,所以我希望以下内容能够起作用。

#include <memory>
#include <string>
class AbstractCamera{
public:
    AbstractCamera(const std::string& name) :_name(name){}
    virtual void method() = 0;
    std::string _name;
};
class CameraImpl : public AbstractCamera{
public:
    CameraImpl() :AbstractCamera("CameraImpl"){}
    void method(){}
};
class RenderManager{
public: 
    RenderManager():_currentCamera(std::move(std::make_unique<CameraImpl>())){}
private:
    std::unique_ptr<AbstractCamera> _currentCamera;
};
class Engine{
public: 
    Engine(){}
private:
    RenderManager r;
};
int main(){
    Engine e;
    e = Engine(); // Causes error: C2280 call of deleted function
}

path\engine.cpp(75):错误C2280:"std::unique_ptr>&std::unique_ptr&lt_Ty,std::default_delete&lt_Ty>>::operator=(const std::unique_ptr<_Ty,std::default_delete<_Ty>>)':试图引用已删除的函数具有[_Ty=AbstractCamera]other_path\memory(1487):请参见'std::unique_ptr>::operator='的声明具有[_Ty=AbstractCamera]此诊断发生在编译器生成的函数"RenderManager&RenderManager::operator=(const RenderManager&)'

我确实得到了像这样的简单例子

std::unique_ptr<A> a = std::make_unique<A>();
std::unique_ptr<A> b = a;

是不允许的,但我的问题是以下代码:

Engine e;
e = Engine();

因为unique_ptr的赋值运算符被删除了,但这对类层次结构(如Engine->RenderMenger->unique_ptrmember)有何影响。

我确实知道,Engine e使用默认构造函数Engine(),e = Engine()调用默认构造函数和Engine的运算符=来将临时Engine对象分配给e。

因此,我的问题是:代码试图在哪里复制/分配unique_ptr,以及如何解决它?

我试图尽可能地删除原始代码,但无法使用更简单的示例和SSCCEE形式的表意符号来重现错误,因为我真的不明白是什么导致了问题,所以很抱歉!

VS2013不会自动生成移动特殊成员函数(如果不明显,这是不符合要求的)。您必须自己编写EngineRenderManager的移动构造函数和移动赋值运算符,否则将使用复制特殊成员函数。

附带说明一下,_currentCamera(std::move(std::make_unique<CameraImpl>))不会编译;CCD_ 6会,但CCD_。make_unique已经返回了一个右值。代码中还有其他一些拼写错误,可能是由于最小化过程造成的。

T.C.正确回答了这个问题,但我想提供一些进一步的见解,因为我实际上问错了问题:)

因此,真正的问题应该是

我如何使用以下代码

Engine e;
e = Engine();

当我的意图是破坏默认构造的对象e并分配新构造的对象时。

首先,我们需要了解复制和移动赋值和构造之间的区别,下面的线程,尤其是@FredOverflow在这里的两篇长帖子,可以帮助我们理解这一点:什么是移动语义?

问题设置实际上是具有std::vectors<std::unique_ptr<T>>成员的类和具有std::unique_ptr<T>成员的类。看见http://www.cplusplus.com/forum/beginner/110610/以及JLJorges的回答作为一个更长的例子。

JLJorges发布:

所以不要做任何事情:默认情况下,B是不可复制或可分配的,但B是可移动和可移动的

JLJorges指出了在这种情况下的两件重要事情:

  • 默认情况下,包含不可复制成员的类不可复制
  • 默认情况下,包含不可复制但可移动/moveAssignable成员的类仍然可以是可移动/moveAssignable

因此,VS2013能够隐式生成move和move-assign运算符/构造函数,就像它为这样的类设置所做的那样。

我没有选择T.C.的答案,尽管他很有帮助,而且接近答案,原因如下:

如果需要可复制/可复制分配的类,则必须实现复制构造函数副本分配。如果我需要最初问题中的代码:一个可复制/可复制的类Engine,那么这就是答案。

如果一个人实现了其中一个方法/运算符,他可能也想实现其他方法/运算符。请参阅以下链接了解解释:

  • 用C++11,三条规则变成五条规则
  • http://en.wikipedia.org/wiki/Rule_of_three_%28C%2B%2B_programming%29#Rule_of_5

正如我所说,我被问错了问题,因为我不需要一个可复制/可复制分配的类,所以解决方案应该是实现移动构造函数和移动分配运算符,或者像JLJorges指出的那样:什么都不做,使用隐式生成的版本,但当我想移动时,我显然必须告诉编译器:

Engine e;
e = std::move(Engine());

e = Engine()调用Engine的移动分配运算符。由于您没有,所以将调用它的副本分配运算符。然后将调用RenderManger的副本分配。最后是unique_ptr的副本分配,它被删除了。

引擎需要移动分配,如

Engine & operator = (Engine && rhs) { r._currentCamera = std::move(rhs.r._currentCamera); return * this; }

假设RenderManager::_currentCamera对Engine可见,否则您需要为RenderManager定义移动分配。