std::move

发布于:2025-03-20 ⋅ 阅读:(18) ⋅ 点赞:(0)

以前也看过一些 std::move 的介绍文章,总结下来一句话:std::move 没有任何代价。这个总结实在是坑人。

今天突然悟了,实际应用中悟的。如果写一个相关的教程,完全不应该从 std::move 入手,而是应该从如何降低对象拷贝代价入手。

struct A {
   int m_a;
   int m_b;
};

如果要对 A 做拷贝,可以写成下面这样。对于这种情况,就算用了 std::move,也没有什么用。

A a1;
g_a2 = a1; // g_a2 是一个类成员

但是,对于下面这个结构,就不一样了:

struct A {
   int m_a;
   int m_b;
   char *m_s;
};

此时,下面的逻辑取决于 A 的拷贝构造函数实现,一般来说,会把 m_a, m_b, 直接赋值,m_s 则会做一个深拷贝。

A a1;
g_a2 = a1; // g_a2 是一个类成员

有些时候,我们不希望做深拷贝。浅拷贝行不行?一般来说是不行的。因为浅拷贝后 g_a2.m_sa1.m_s 会指向同一片内存区域,并且 a1 是栈变量,它析构后会释放 m_s 指向的内存,那么 g_a2 就会得到一个野指针。

怎么办呢?C++ 发明了一种“移动”语义,让 A 实现下面这样一个移动拷贝函数:

struct A {
    // 默认构造
    A() : m_a(0), m_b(0.0), m_s(nullptr) {}
    // 带参数构造
    A(int a, int b, const std::string& s) 
        : m_a(a), m_b(b), m_s(new std::string(s)) {}
    ~A() { delete m_s; } // 释放内存
    // 移动构造
    A(A&& other) {
        m_a = other.m_a;
        m_b = other.m_b;
        m_s = other.m_s;
        other.m_s = nullptr; // 关键之处,强行删掉 other.m_s 的指针,实现了内存的“移动”
    }
    // 移动赋值
    A& operator=(A&& other) {
		if (this != &other) { // 防止自赋值
		   m_a = other.m_a;
		   m_b = other.m_b;
		   m_s = other.m_s;
		   other.m_s = nullptr; // 关键之处,强行删掉 other.m_s 的指针,实现了内存的“移动”
		}
		return *this;
    }

};

A 还可以这样定义这个函数:

struct A {
    void set(A&& other) {
        m_a = other.m_a;
        m_b = other.m_b;
        m_s = other.m_s;
        other.m_s = nullptr;
    }
};

A a1;
g_a2.set(std::move(a1)); // g_a2 是一个类成员

所以,std::move 的确什么事情都不做,它只是提示编译器,去调用支持 move 语义的函数而已,具体做事情的,是这些函数。

Note:STL 中,vector 的 emplace_back、push_back 等也实现了支持 move 语义等版本。


网站公告

今日签到

点亮在社区的每一天
去签到