提供正确的移动语义

7
我目前正在尝试正确地使用移动语义处理包含指向分配内存的指针的对象。我有一个大数据结构,其中包含对实际存储的内部原始指针(出于效率原因)。现在我添加了一个移动构造函数和移动operator=()。在这些方法中,我将指针std::move()到新结构中。但是我不确定如何处理来自其他结构的指针。
以下是我正在执行的简单示例:
class big_and_complicated {
   // lots of complicated code
};

class structure {
public:
   structure() :
      m_data( new big_and_complicated() )
   {}

   structure( structure && rhs ) :
      m_data( std::move( rhs.m_data ) )
   {
      // Maybe do something to rhs here?
   }

   ~structure()
   {
      delete m_data;
   }

private:
   big_and_complicated * m_data;
}

int main() {
  structure s1;
  structure s2( std::move( s1 ) );
  return 0;
}

现在我所理解的是,经过将 std::move( s1 ) 移动到 s2 后,唯一安全的操作是调用 s1 的构造函数。然而据我所知,这会导致在析构函数中删除 s1 中包含的指针,从而使 s2 也变得无用。因此,我猜测在对指针进行 std::move() 时,我必须做一些工作以使析构函数变得安全。据我所见,最安全的做法是将其设置为移动后对象中的 0,因为这样可以将 delete 变为以后的 no-op。迄今为止,我的推理是否正确?或者说,std::move() 是否真正聪明地将指针置空,使其使用安全?到目前为止,在实际测试套件中我没有遇到崩溃,但我还没有确定移动构造函数实际上是否被调用。
1个回答

20

“移动”指针并不比复制指针更有区别,并且不会将被移动的值设置为 null(这里“移动”使用引号是因为 std::move 并不是真的移动任何东西,它只是改变了参数的值类别)。只需复制 rhs 的指针,然后将其设置为 nullptr

struct structure
{
    structure()
      : m_data{new big_and_complicated{}}
    { }

    structure(structure&& rhs)
      : m_data{rhs.m_data}
    {
        rhs.m_data = nullptr;
    }

    structure& operator =(structure&& rhs)
    {
        if (this != &rhs)
        {
            delete m_data;
            m_data = rhs.m_data;
            rhs.m_data = nullptr;
        }
        return *this;
    }

    ~structure()
    {
        delete m_data;
    }

private:
    big_and_complicated* m_data;

    structure(structure const&) = delete;             // for exposition only
    structure& operator =(structure const&) = delete; // for exposition only
}
你也可以通过使用std::exchange来简化这个过程:
structure(structure&& rhs)
    : m_data{ std::exchange(rhs.m_data, nullptr) }
{ }

structure& operator=(structure&& rhs)
{
    if (this != &rhs)
    {
        delete m_data;
        m_data = std::exchange(rhs.m_data, nullptr);
    }
    return *this;
}

更好的做法是使用std::unique_ptr<big_and_complicated>而不是big_and_complicated*,这样你就不需要自己定义任何东西:

#include <memory>

struct structure
{
    structure()
      : m_data{new big_and_complicated{}}
    { }

private:
    std::unique_ptr<big_and_complicated> m_data;
}

最后,除非你确实希望structure保持不可复制状态,否则最好在big_and_complicated内部实现适当的移动语义,并直接使structure持有一个big_and_complicated对象。


@ildjarn:不幸的是,直接持有big_and_complicated是不可行的,因为我需要稍后将此指针存储在其他位置。其他位置依赖于结构(考虑:迭代器),所以这是可以的。如果有人保留迭代器比实际结构更长的时间,那就是他们的问题。 - LiKao
@LiKao :听起来像是std::shared_ptr<>std::weak_ptr<>的可能候选。 :-] - ildjarn
ildjarn:那是我之前写的,但后来我进行了优化。 - LiKao
答案中的错误是否已经被修复了? - Candy Chiu
@Candy:如果你指的是Howard提到的那个问题,是的,那个问题很快就被解决了。:-] - ildjarn

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