C++ 中的移动语义 Move Semantics
有时候拷贝对象的成本太高, 会显著影响 C++ 代码的性能.
C++11 之前的世界
之前的 C++ 版本中, 鼓励以复制的方式来构造对象, 比如:
std::string s1 = "C++";
std::string s2 = s1;
上面的代码中, 字符串 s2
会复制一份与 s1
相同的堆内存, 这之后它们两个不再有任何关联.
就像下图展示的那样:
下面是一个更复杂的例子:
#include <cstdio>
#include <cassert>
#include <string>
class Person {
public:
explicit Person(const std::string& name) noexcept : name_(name) {
printf("Person(const string&) %s\n", name_.c_str());
}
Person(const Person& other) : name_(other.name_) {
printf("Person(const Person&)\n");
}
Person(Person&& other) noexcept = delete;
~Person() = default;
Person& operator=(const Person& other) {
if (this == &other) {
return *this;
}
this->name_ = other.name_;
return *this;
}
Person& operator=(Person&& other) noexcept = delete;
const std::string& name() const { return name_; }
private:
std::string name_;
};
int main(int argc, char** argv) {
(void)argc;
(void)argv;
std::string name = "Julia";
Person p2(name);
assert(!name.empty());
name.clear();
printf("creating p3\n");
Person p3("Julia");
// 使用 copy constructor
printf("creating p4:\n");
Person p4(p3);
return 0;
}
为了简化图例, 本文忽略了 std::string
相关的 SSO (short string optimization), 但这对本文的核心没有影响.
更多关于 SSO 的信息可以参考本文结尾的链接.
C++11 引入 移动语义 Move semantics
移动语义依赖三个基础:
- move constructor
- move assignment operator
std::move()
这是对 C++ 过渡封装的补救.
- 一个右值引用参数
- 转移所有权
- 原有的对象仍然保持有效状态
- 它是浅拷贝
C++ 中的右值引用 Rvalue Reference
std::move()
将左值 (lvalue) 对象转换成对应的右值引用(rvalue reference).
什么是左值 lvalue?
- 可以出现在赋值表达式的左侧
- 有名字
- 有内存地址
什么是右值 rvalue?
- 除了不是 lvalue 的, 都是 rvalue
- 临时对象
- 字面量常量 literal constants, 比如
"Hello, C++"
- 函数返回值 (不是左值引用 lvalue reference)
std::string s1 = "C++";
std::string s2 = std::move(s1);
上面的代码片段中, 字符串 s2
是 s1
原有内存的浅拷贝; 而 s1
里面的堆内存被重新设置了,
并且其字符串长度 size == 0
.
下面是一个更复杂的例子, Person
类额外实现了
- move constructor
- move assignment operator
在创建对象时可以使用它们进行浅拷贝, 以提高程序的速度.
#include <cstdio>
#include <cassert>
#include <string>
class Person {
public:
explicit Person(std::string&& name) noexcept : name_(std::move(name)) {
printf("Person(string&&) %s\n", name_.c_str());
}
Person(const Person& other) : name_(other.name_) {
printf("Person(const Person&)\n");
}
Person(Person&& other) noexcept : name_(std::move(other.name_)) {
printf("Person(Person&&)\n");
}
~Person() = default;
Person& operator=(const Person& other) {
if (this == &other) {
return *this;
}
this->name_ = other.name_;
return *this;
}
Person& operator=(Person&& other) noexcept {
if (this == &other) {
return *this;
}
this->name_ = std::move(other.name_);
return *this;
}
const std::string& name() const { return name_; }
private:
std::string name_;
};
int main(int argc, char** argv) {
(void)argc;
(void)argv;
std::string name = "Julia";
Person p2(std::move(name));
assert(name.empty());
printf("creating p3\n");
Person p3("Julia");
// 使用 copy constructor
printf("creating p4:\n");
Person p4(p3);
// 使用 move constructor
printf("creating p5:\n");
Person p5(std::move(p4));
return 0;
}