包含独有指针成员变量的类的C++赋值运算符

4
我知道,如果一个类有一个智能独占指针,则不可能将该类分配给另一个实例,因为独占指针无法复制。我知道我可以将唯一指针更改为共享指针,这样就可以解决问题。但是,如果我不想共享指针所有权怎么办?是否可以创建一个赋值运算符来移动唯一指针并复制其他变量?
我已经阅读过,您可以使用std::move来传递所有权。
#include <iostream>
#include <memory> 

struct GraphStructure { };

class test {

        int a;
        std::vector<int> vector;
        std::unique_ptr<GraphStructure> Graph_; 
 };

 int main () {

     test t1;
     auto t2 = t1;
 }

2
移动会使t1变得无用,您想要解决什么确切的问题? - πάντα ῥεῖ
@πάνταῥεῖ 在给定的示例中移动是没有意义的,但是与std::vector结合使用时,我们需要移动以进行重新分配。 - Aconcagua
1
你可以创建一个移动赋值运算符,它可以移动所有东西(在这种情况下,使用 test& operator=(test&& other) = default;),但这也会移动向量而不是复制它。如果你手动实现它,你可以做你要求的事情(你是否在尝试中卡住了),但它不会像大多数使用你的类的人所期望的那样行为良好,所以我强烈建议不要这样做。 - Daniel H
2
另外,在 main 函数中,您从未使用赋值运算符。第二行调用了复制构造函数(由于 unique_ptr 的隐式删除),而不是复制赋值运算符,尽管它使用了 = 语法。 - Daniel H
你可能想要复制另一个对象的Graph_拥有的GraphStructure,而不是窃取它。这是赋值(和初始化,也就是你正在做的事情)的“正常”行为,如果你使用原始指针,那么你会这样做。 - molbdnilo
2个回答

6

由于成员变量graph_无法进行复制(如果你仍然可以以有意义的方式进行复制,例如通过创建图成员的深层副本,则必须自己实现拷贝构造函数),所以默认的复制构造函数被删除了。与此相反,std::unique_ptr可移动,因此默认的移动构造函数仍然存在。因此,您可以执行以下操作:

test t1;
auto t2 = std::move(t1);

需要注意的是,t1之后就不再持有任何对象(您移动了该对象,因此您将其内容移到了另一个对象中),t2之前持有的对象也被销毁了。这是否是一种有意义的状态取决于您自己的决定...

附注:我所写的关于复制和移动构造函数的内容同样适用于复制和移动赋值运算符...


3

简单的修复方法

如果 GraphStructure 是一个没有任何虚成员函数的类或结构体,那么这个问题很容易解决。我们可以编写一个函数来复制 unique_ptr 中的数据,以创建一个新的 GraphStructure:

std::unique_ptr<GraphStructure> duplicate(std::unique_ptr<GraphStructure> const& ptr)
{
    return std::make_unique<GraphStructure>(*ptr);
}

一旦我们拥有了duplicate类,我们就可以使用这个类来编写一个用于测试的复制构造函数:
class test {
    std::unique_ptr<GraphStructure> ptr;
    std::vector<int> values;
   public:
    // this can be defaulted
    test() = default;
    // we use duplicate to create a copy constructor
    test(const test& source) 
      : ptr(duplicate(source.ptr)))
      , values(source.values)
    {}
    // we can use the default move constructor
    test(test&&) = default;

    test& operator=(test const& source) {
        ptr = duplicate(source.ptr);
        values = source.values; 
        return *this;
    }
    // we can use the default move assignment operator 
    test& operator=(test&&) = default;
};

如果GraphStructure有虚方法会怎样?

在这种情况下,向GraphStructure添加一个名为clone的虚函数,返回一个新的std::unique_ptr<GraphStructure>

class GraphStructure {
   public:
    // override this method in child classes
    virtual std::unique_ptr<GraphStructure> clone() {
        return std::make_unique<GraphStructure>(*this);
    }
    virtual ~GraphStructure() {}
};

然后,使用.clone()代替duplicate


2
通常情况下,你应该优先使用 make_unique 而不是 unique_ptr<T>(new ...),因为如果表达式出现问题,则没有相应的 delete 来匹配你的 new,此时就会产生内存泄漏。参见 https://herbsutter.com/gotw/_102/ (除了 make_unique 现在是一个标准函数,而不是你需要编写的内容)。 - Daniel H
如果new成功完成,则函数中不可能出现任何错误,因为std::unique_ptr的构造函数由标准保证是noexcept的。 - Alecto Irene Perez
1
@J.AntonioPerez 在这种情况下是正确的,因为表达式很简单。但是,如果您在其他未排序操作的表达式中使用原始指针构造函数,则在newunique_ptr构造函数之间可能会抛出某些内容并泄漏内存。您代码中的行没有错误,只是没有强化良好习惯,这是在帮助学习智能指针的人时需要考虑的问题。 - patatahooligan
如果GraphStructure是一个没有任何虚成员函数的类或结构体,则应该提供一些Clone方法。 - Jarod42
谢谢 - 我已更新代码以显示一个clone()方法并使用make_unique。 - Alecto Irene Perez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接