Skip to content

Ch 24: 移动语义

  • 理解移动语义解决的核心问题(避免不必要的拷贝)
  • 掌握右值引用 T&& 的语义和用法
  • 学会实现移动构造函数和移动赋值运算符
  • 熟练使用 std::move 和 std::forward
  • 理解移动语义在容器和返回值优化中的应用
# Python - 赋值是引用拷贝
a = [1, 2, 3] # a 指向列表对象
b = a # b 也指向同一个列表对象
b.append(4) # 修改的是同一个对象
print(a) # [1, 2, 3, 4] - a 也被修改!
# 要独立副本需要 copy
c = a.copy() # 或 list(a) 或 a[:]
c.append(5) # c 修改,a 不变
#include <vector>
#include <iostream>
int main() {
std::vector<int> v1 = {1, 2, 3};
// 拷贝 - 复制所有元素
std::vector<int> v2 = v1; // v1 不变,v2 是独立副本
// 移动 - 转移所有权,不复制数据
std::vector<int> v3 = std::move(v1);
// v1 变为空!v3 接管 v1 的内部数据
// 不需要分配新内存,不复制元素
std::cout << "v2 size: " << v2.size() << "\n"; // 3
std::cout << "v3 size: " << v3.size() << "\n"; // 3
std::cout << "v1 size: " << v1.size() << "\n"; // 0(空)
return 0;
}
#include <vector>
#include <string>
#include <iostream>
// 假设有大型对象
class HeavyObject {
public:
HeavyObject() {
std::cout << "Default construct\n";
}
HeavyObject(const HeavyObject&) {
std::cout << "Copy construct - expensive!\n";
}
HeavyObject(HeavyObject&&) noexcept {
std::cout << "Move construct - cheap!\n";
}
};
int main() {
std::vector<HeavyObject> vec;
// push_back 需要拷贝
vec.push_back(HeavyObject()); // 移动而不是拷贝
// emplace_back 直接构造
vec.emplace_back(); // 直接构造,不移动不拷贝
return 0;
}
#include <iostream>
#include <utility>
int main() {
int x = 42;
// 左值 - 有名字,可以取地址
int& lref = x; // 左值引用
const int& clref = x; // const 左值引用
// 右值 - 没有持久名字,临时对象
int&& rref = 42; // 右值引用,绑定到临时对象
int&& rref2 = x + 1; // x+1 是临时(右值)
// 常见右值
42; // 字面量
std::string("hello"); // 临时对象
x + 1; // 表达式结果
std::move(x); // std::move 返回右值引用
// 左值引用绑定规则
// int& r1 = 42; // ❌ 错误!左值引用不能绑定到右值
// const int& r2 = 42; // ✅ OK!const 引用可以绑定到右值
// 右值引用绑定规则
// int&& r3 = x; // ❌ 错误!右值引用不能绑定到左值
int&& r4 = 42; // ✅ OK
int&& r5 = std::move(x); // ✅ OK!std::move 把左值转右值引用
return 0;
}
#include <iostream>
#include <utility>
// 左值引用
void process(int& value) {
std::cout << "Lvalue ref: " << value << "\n";
}
// 右值引用
void process(int&& value) {
std::cout << "Rvalue ref: " << value << "\n";
}
int main() {
int x = 10;
process(x); // 调用 Lvalue ref(x 是左值)
process(10); // 调用 Rvalue ref(10 是右值)
process(x + 5); // 调用 Rvalue ref(x+5 是右值)
process(std::move(x)); // 调用 Rvalue ref
return 0;
}
#include <vector>
#include <string>
#include <iostream>
class Buffer {
public:
Buffer(std::size_t size) : size_(size), data_(new char[size]) {
std::cout << "Construct buffer of size " << size_ << "\n";
}
~Buffer() {
delete[] data_;
std::cout << "Destroy buffer\n";
}
// 拷贝构造函数
Buffer(const Buffer& other)
: size_(other.size_), data_(new char[other.size_]) {
std::cout << "Copy buffer\n";
std::copy(other.data_, other.data_ + other.size_, data_);
}
// 移动构造函数
Buffer(Buffer&& other) noexcept
: size_(other.size_), data_(other.data_) {
std::cout << "Move buffer\n";
other.data_ = nullptr;
other.size_ = 0;
}
std::size_t size() const { return size_; }
private:
std::size_t size_;
char* data_;
};
int main() {
std::cout << "=== Creating b1 ===\n";
Buffer b1(1000);
std::cout << "\n=== Copying b1 to b2 ===\n";
Buffer b2 = b1; // 拷贝(分配内存,复制数据)
std::cout << "\n=== Moving b1 to b3 ===\n";
Buffer b3 = std::move(b1); // 移动(只是转移指针,不分配内存)
std::cout << "\n=== b1 after move ===\n";
std::cout << "b1 size: " << b1.size() << "\n"; // 0(空)
std::cout << "\n=== End of main ===\n";
return 0;
}
#include <utility>
#include <iostream>
#include <cstring>
class String {
public:
// 构造
String(const char* s) {
std::size_t len = std::strlen(s);
data_ = new char[len + 1];
std::copy(s, s + len + 1, data_);
std::cout << "Construct: " << data_ << "\n";
}
// 拷贝构造
String(const String& other) {
std::size_t len = std::strlen(other.data_);
data_ = new char[len + 1];
std::copy(other.data_, other.data_ + len + 1, data_);
std::cout << "Copy: " << data_ << "\n";
}
// 移动构造
String(String&& other) noexcept {
data_ = other.data_; // 转移资源
other.data_ = nullptr; // 防止析构时删除
std::cout << "Move: " << data_ << "\n";
}
~String() {
delete[] data_;
}
const char* c_str() const { return data_ ? data_ : "(null)"; }
private:
char* data_ = nullptr;
};
int main() {
String s1("Hello");
std::cout << "s1: " << s1.c_str() << "\n\n";
std::cout << "=== Copying s1 to s2 ===\n";
String s2 = s1;
std::cout << "s2: " << s2.c_str() << "\n\n";
std::cout << "=== Moving s1 to s3 ===\n";
String s3 = std::move(s1);
std::cout << "s3: " << s3.c_str() << "\n";
std::cout << "s1 after move: " << s1.c_str() << "\n\n";
return 0;
}
#include <vector>
#include <iostream>
class Movable {
public:
Movable() = default;
// noexcept 移动操作不会触发异常
// C++11/14 中 vector 在 realloc 时会使用移动而不是拷贝
Movable(Movable&& other) noexcept : data_(other.data_) {
other.data_ = nullptr;
}
Movable& operator=(Movable&& other) noexcept {
if (this != &other) {
delete[] data_;
data_ = other.data_;
other.data_ = nullptr;
}
return *this;
}
~Movable() { delete[] data_; }
private:
int* data_ = nullptr;
};
int main() {
std::vector<Movable> vec;
// 如果移动构造函数是 noexcept,vector realloc 时会使用移动
// 如果没有 noexcept,vector 会使用拷贝(更安全但更慢)
for (int i = 0; i < 100; ++i) {
vec.push_back(Movable());
}
return 0;
}
#include <iostream>
#include <utility>
class Buffer {
public:
Buffer(std::size_t size) : size_(size), data_(new int[size]) {
std::cout << "Construct Buffer(" << size_ << ")\n";
}
// 移动赋值运算符
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) { // 自赋值检查
delete[] data_; // 释放原有资源
data_ = other.data_;
size_ = other.size_;
other.data_ = nullptr; // 防止析构
other.size_ = 0;
std::cout << "Move assignment\n";
}
return *this;
}
// 拷贝赋值运算符
Buffer& operator=(const Buffer& other) {
if (this != &other) {
delete[] data_;
size_ = other.size_;
data_ = new int[size_];
std::copy(other.data_, other.data_ + size_, data_);
std::cout << "Copy assignment\n";
}
return *this;
}
~Buffer() {
delete[] data_;
std::cout << "Destroy Buffer(" << size_ << ")\n";
}
std::size_t size() const { return size_; }
private:
std::size_t size_;
int* data_;
};
int main() {
Buffer b1(100);
Buffer b2(50);
std::cout << "\n=== b2 = std::move(b1) ===\n";
b2 = std::move(b1); // 移动赋值
std::cout << "b2 size: " << b2.size() << "\n";
std::cout << "\n=== End of main ===\n";
return 0;
}
#include <utility>
#include <iostream>
#include <vector>
#include <string>
int main() {
// std::move 将左值转为右值引用
int x = 42;
int&& r = std::move(x); // x 变为右值
std::string s1 = "Hello";
std::string s2 = std::move(s1); // s1 变为空
std::cout << "s2: " << s2 << "\n";
std::cout << "s1: " << (s1.empty() ? "(empty)" : s1) << "\n";
// 在容器中使用
std::vector<std::string> vec;
std::string s = "World";
vec.push_back(std::move(s)); // 移动而不是拷贝
std::cout << "vec[0]: " << vec[0] << "\n";
std::cout << "s after move: " << (s.empty() ? "(empty)" : s) << "\n";
return 0;
}
#include <utility>
#include <iostream>
#include <string>
// 模板参数推导
template<typename T>
void process_value(T value) {
// 传入什么就转发什么
std::cout << "process_value\n";
}
template<typename T>
void process_lvalue(T& value) {
std::cout << "process_lvalue (T&)\n";
}
template<typename T>
void process_rvalue(T&& value) {
std::cout << "process_rvalue (T&&)\n";
}
// 完美转发:保持原始值类别
template<typename T>
void wrapper(T&& arg) { // 万能引用
// std::forward<T> 转发时保持原始值类别
// 如果传入左值,转发为左值引用
// 如果传入右值,转发为右值引用
process_value(std::forward<T>(arg));
// 或根据需要选择
// process_lvalue(arg);
// process_rvalue(std::move(arg));
}
void take_string(std::string& s) {
std::cout << "Lvalue string: " << s << "\n";
}
void take_string(std::string&& s) {
std::cout << "Rvalue string: " << s << "\n";
}
template<typename T>
void wrapper2(T&& arg) {
take_string(std::forward<T>(arg));
}
int main() {
std::string s = "Hello";
// 传入左值
wrapper(s); // T = std::string&
wrapper("world"); // T = const char*(触发类型转换)
// 传入右值
wrapper(std::string("temp"));
// 完美转发保持值类别
wrapper2(s); // 调用 lvalue 版本
wrapper2(std::string("temp")); // 调用 rvalue 版本
return 0;
}
#include <utility>
#include <iostream>
// std::move 总是转发为右值
template<typename T>
void caller1(T&& arg) {
// std::move 强制转右值
process(std::move(arg)); // 总是调用右值版本
}
// std::forward 保持原始值类别
template<typename T>
void caller2(T&& arg) {
// std::forward 保持左/右属性
process(std::forward<T>(arg)); // 传入什么转发什么
}
void process(int&) { std::cout << "Lvalue\n"; }
void process(int&&) { std::cout << "Rvalue\n"; }
int main() {
int x = 42;
caller1(x); // 转发为右值,调用 process(int&&)
caller1(42); // 转发为右值
caller2(x); // 转发为左值,调用 process(int&)
caller2(42); // 转发为右值,调用 process(int&&)
return 0;
}
#include <iostream>
#include <vector>
#include <string>
// 情况 1:返回局部对象
std::vector<int> create_vector() {
std::vector<int> result = {1, 2, 3};
return result; // 可能触发移动或拷贝(C++17 保证移动)
}
// 情况 2:直接构造返回值
std::vector<int> create_vector2() {
return std::vector<int>{1, 2, 3}; // 可能触发 RVO
}
// 情况 3:命名返回值优化(NRVO)
std::vector<int> create_vector3() {
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
return v; // 编译器可能优化,不移动直接构造到返回值位置
}
// C++17 保证 RVO
template<typename T>
T make_holder(T&& value) {
return std::forward<T>(value); // C++17 保证不拷贝不移动
}
int main() {
auto v1 = create_vector();
auto v2 = create_vector2();
std::cout << "v1 size: " << v1.size() << "\n";
std::cout << "v2 size: " << v2.size() << "\n";
return 0;
}
#include <string>
#include <iostream>
class Person {
public:
Person(const std::string& n) : name(n) {
std::cout << "Construct: " << name << "\n";
}
Person(const Person& p) : name(p.name) {
std::cout << "Copy: " << name << "\n";
}
Person(Person&& p) noexcept : name(std::move(p.name)) {
std::cout << "Move: " << name << "\n";
}
Person& operator=(const Person& p) {
name = p.name;
std::cout << "Copy assign: " << name << "\n";
return *this;
}
Person& operator=(Person&& p) noexcept {
name = std::move(p.name);
std::cout << "Move assign: " << name << "\n";
return *this;
}
private:
std::string name;
};
Person create_person1() {
Person p("Alice");
return p; // NRVO 可能发生(编译器决定)
}
Person create_person2() {
return Person("Bob"); // RVO 可能发生
}
int main() {
std::cout << "=== create_person1 ===\n";
Person p1 = create_person1();
std::cout << "\n=== create_person2 ===\n";
Person p2 = create_person2();
std::cout << "\n=== Move into existing ===\n";
Person p3("Charlie");
p3 = std::move(p1); // 移动赋值
return 0;
}
#include <vector>
#include <iostream>
#include <string>
class Item {
public:
std::string name;
Item(const std::string& n) : name(n) {
std::cout << "Construct: " << name << "\n";
}
Item(Item&& other) noexcept : name(std::move(other.name)) {
std::cout << "Move: " << name << "\n";
}
Item& operator=(Item&& other) noexcept {
name = std::move(other.name);
std::cout << "Move assign: " << name << "\n";
return *this;
}
};
int main() {
std::vector<Item> vec;
std::cout << "=== emplace_back ===\n";
vec.emplace_back("Apple"); // 直接构造,不移动
std::cout << "\n=== push_back with move ===\n";
Item item("Banana");
vec.push_back(std::move(item)); // 移动
std::cout << "\n=== push_back with temp ===\n";
vec.push_back(Item("Cherry")); // 临时对象可能直接构造
std::cout << "\n=== End ===\n";
return 0;
}
概念说明
左值有名字,持久存在,可以取地址
右值临时对象,没有持久名字
左值引用 T&绑定左值
右值引用 T&&绑定右值(临时对象)
std::move将左值转为右值引用
std::forward<T>保持原始值类别转发
移动构造函数转移资源所有权,不复制数据
移动赋值运算符释放当前资源,转移新资源所有权

使用原则:

  • 拷贝构造复制数据,移动构造转移所有权
  • noexcept 保证让容器在 realloc 时使用移动
  • std::move 用于将左值转右值(转移所有权)
  • std::forward 用于模板参数转发(保持值类别)
  • 移动后原对象处于有效但未定义状态

下章预告:ch25 学习 RAII 模式,理解资源获取即初始化的核心思想。