提供正确的移动语义

发布于 2024-12-29 14:36:12 字数 1051 浏览 1 评论 0原文

我目前正在尝试弄清楚如何使用包含指向已分配内存的指针的对象正确执行移动语义。我有一个大数据结构,其中包含指向实际存储的内部原始指针(出于效率原因)。现在我添加了一个移动构造函数和移动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 稍后变成无操作在。到目前为止这个推理正确吗?或者 std::move() 实际上足够聪明,可以为我清空指针,使其使用安全吗?到目前为止,我在实际的测试套件中没有看到崩溃,但我还没有确保移动构造函数确实被调用。

I am currently trying to figure out how to do move semantics correctly with an object which contains a pointer to allocated memory. I have a big datastructure, which contains an internal raw pointer to the actual storage (for efficiency reasons). Now I added a move constructor and move operator=(). In these methods I am std::move()ing the pointer to the new structure. However I am not sure what to do with the pointer from the other structure.

Here is a simple example of what I am doing:

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;
}

Now from what I understand, after the std::move( s1 ) to s2 the only thing that is safe to do on s1 ist to call its constructor. However as far as I can see, this would lead to deleting the pointer contained within s1 in the destructor, rendering s2 useless as well. So I am guessing I have to do something to render the destructor safe when std::move()ing the pointer. As far as I can see the safest thing to do here, is to set it to 0 in the moved object, since this would turn the delete into a no-op later on. Is this reasoning correct so far? Or is std::move() actually smart enough to null out the pointer for me, rendering its usage safe? So far I am seeing no crashes in the actual test-suite, but I have not made sure the move-constructor is actually called.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

鲜血染红嫁衣 2025-01-05 14:36:12

“移动”指针与复制指针没有什么不同,并且不会将移动源值设置为 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;
}

更好的是,使用 < code>std::unique_ptr而不是 big_and_complicated* 并且您不需要自己定义任何这些:

#include <memory>

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

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

最后,除非您确实想要struct 要保持不可复制,最好在 big_and_complicated 内部实现适当的移动语义,并让 struct 保存 big_and_complicated直接对象。

"Moving" a pointer is no different than copying one and does not set the moved-from value to null ('moving' is in quotes here because std::move does not move anything really, it just changes the value category of the argument). Just copy rhs's pointer then set it to 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
}

You could also simplify this by using 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;
}

Better yet, use std::unique_ptr<big_and_complicated> instead of big_and_complicated* and you don't need to define any of this yourself:

#include <memory>

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

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

Lastly, unless you actually want structure to remain non-copyable, you're better off just implementing proper move semantics inside of big_and_complicated and having structure hold a big_and_complicated object directly.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文