繁体   English   中英

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

[英]Should use unique_ptr to more easily implement “move” semantics?

编辑:使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;
    }
};

随着数据成员的添加,实施移动变得更加繁琐。 但是,可移动数据可以放在单独的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;
};

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

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

所以Foo会成为:

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

public:
    Foo(size_t size): data(new size_t[size]) { }
};

这很容易证明正确性。

这被称为零规则。

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

5规则规定,如果你实施5个复制/移动分配/转移器或者dtor中的任何一个,你应该实现或删除它们中的全部5个(或者,在适当考虑之后,默认它们)。

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

部分原因是您不再直接调用new new是直接管理资源的5规则类型中的实现细节。 业务逻辑类不会弄乱new 它们既不新,也不删除。

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

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

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

“业务逻辑” std::function不会实现复制/移动assign / ctor,也不会实现析构函数。 它可能会显式=default它们。

我的建议是将问题分开使用构图

管理已分配内存的生命周期是智能指针的工作。 如何将内存(或其他资源)返回到运行时是智能指针删除器的关注点。

一般来说,如果您发现自己编写移动运算符并移动构造函数,那是因为您没有充分分解问题。

例:

#include <cstring>
#include <memory>

// a deleter
//
struct delete_or_free
{
    void operator()(int* p) const 
    {
      if (free_) {
        std::free(p);
    }
      else {
        delete [] p;
      }
    }

  bool free_;
};


class Foo
{
  //
  // express our memory ownership in terms of a smart pointer.
  //
  using ptr_type = std::unique_ptr<int[], delete_or_free>;
  ptr_type ptr_;

  // other members ...

  //
  // some static helpers (reduces clutter in the constructor)
  //
  static auto generate_new(int size) {
    return ptr_type { new int[size], delete_or_free { false } };
  }

  static auto generate_calloc(int size) {
    return ptr_type { 
      static_cast<int*>(calloc(size, sizeof(int))),
      delete_or_free { true } 
    };
  }

public:

    //
    // our one and only constructor
    //
    Foo(size_t num, bool useNew=true) 
      : ptr_ { useNew ? generate_new(num) : generate_calloc(num) }
    {
    }

    // it's good manners to provide a swap, but not necessary.   
    void swap(Foo& other) noexcept {
      ptr_.swap(other.ptr_);
    }
};

//
// test
//
int main()
{
  auto a = Foo(100, true);
  auto b = Foo(200, false);

  auto c = std::move(a);
  a = std::move(b);
  b = std::move(c);

  std::swap(a, b);
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM