应该使用unique_ptr更容易实现"移动"语义吗?

Ðаn*_*Ðаn 2 c++ unique-ptr move-semantics c++11

编辑:制作FooBar稍微不那么琐碎,直接替换shared_ptr<>更难.


应该unique_ptr<>用作实现移动语义的更简单方法吗?

对于像这样的课程

class Foo
{
    int* m_pInts;
    bool usedNew;
    // other members ...

public:
    Foo(size_t num, bool useNew=true) : usedNew(useNew) {
        if (usedNew)
            m_pInts = new int[num];
        else
            m_pInts = static_cast<int*>(calloc(num, sizeof(int)));
    }
    ~Foo() {
        if (usedNew)
            delete[] m_pInts;
        else
            free(m_pInts);
    }

    // no copy, but move
    Foo(const Foo&) = delete;
    Foo& operator=(const Foo&) = delete;
    Foo(Foo&& other) {
        *this = std::move(other);
    }
    Foo& operator=(Foo&& other) {
        m_pInts = other.m_pInts;
        other.m_pInts = nullptr;
        usedNew = other.usedNew;
        return *this;
    }
};
Run Code Online (Sandbox Code Playgroud)

随着数据成员的添加,实施移动变得更加繁琐.但是,可移动数据可以放在一个单独的struct,其实例由其管理unique_ptr<>.这允许=default用于移动:

class Bar
{
    struct Data
    {
        int* m_pInts;
        bool usedNew;
        // other members ...
    };
    std::unique_ptr<Data> m_pData = std::make_unique<Data>();

public:
    Bar(size_t num, bool useNew = true) {
        m_pData->usedNew = useNew;
        if (m_pData->usedNew)
            m_pData->usedNew = new int[num];
        else
            m_pData->m_pInts = static_cast<int*>(calloc(num, sizeof(int)));
    }
    ~Bar() {
        if (m_pData->usedNew)
            delete[] m_pData->m_pInts;
        else
            free(m_pData->m_pInts);
    }

    // no copy, but move
    Bar(const Bar&) = delete;
    Bar& operator=(const Bar&) = delete;
    Bar(Bar&& other) = default;
    Bar& operator=(Bar&& other) = default;
};
Run Code Online (Sandbox Code Playgroud)

除了unique_ptr<>实例的内存总是在堆上之外,这样的实现还存在哪些其他问题?

Bar*_*rry 6

是.您正在寻找的是零规则(作为三/五规则的C++ 11扩展).通过让您的数据都知道如何复制和移动自己,外部类不需要编写任何特殊的成员函数.编写这些特殊成员可能容易出错,因此不必编写它们就可以解决很多问题.

所以Foo会成为:

class Foo
{
    std::unique_ptr<size_t[]>  data;

public:
    Foo(size_t size): data(new size_t[size]) { }
};
Run Code Online (Sandbox Code Playgroud)

这很容易证明正确性.


Yak*_*ont 5

这就是所谓的“零法则”。

零规则规定大多数类不实现复制/移动分配/构造或销毁。相反,您将其委托给资源处理类。

5 条规则规定,如果您实现 5 个复制/移动分配/ctor 或 dtor 中的任何一个,您应该实现或删除所有 5 个(或者,经过适当考虑后,默认它们)。

在您的情况下,m_pInts应该是一个唯一的指针,而不是原始内存处理的缓冲区。如果它与某些东西(例如大小)相关,那么您应该编写一个实现 5 规则的指针和大小结构。或者,std::vector<int>如果 3 个指针而不是 2 个指针的开销是可以接受的,则只需使用 a 。

其中一部分是您停止new直接调用。 new是直接管理资源的 5 规则类型中的实现细节。业务逻辑类不要搞乱new。它们既不新建,也不删除。

unique_ptr只是资源管理类型中的一种。 std::string, std::vector, std::set, shared_ptr, std::future, std::function-- 大多数 C++std类型都符合条件。自己编写也是一个好主意。但是当你这样做时,你应该从“业务逻辑”中剥离资源代码。

因此,如果您编写了一个std::function<R(Args...)>克隆,您可以使用 aunique_ptr或 aboost::value_ptr来存储函数对象的内部内容。也许您甚至会编写sbo_value_ptr有时存在于堆上、有时存在于本地的 a 。

然后你可以用“业务逻辑”来包装它,它std::function理解所指向的东西是可调用的等等。

“业务逻辑”std::function不会实现复制/移动分配/构造函数,也不会实现析构函数。他们可能会=default明确表示。