C++ 移动语义
概述
移动语义是C++11引入的重要特性,通过"移动"而不是"复制"来传递资源,显著提升了程序性能。移动语义基于右值引用实现,避免了不必要的深拷贝操作,特别适用于管理动态资源的类。
📦 右值引用基础
左值和右值
cpp
#include <iostream>
#include <string>
#include <vector>
class ValueCategoryDemo {
public:
static void demonstrateCategories() {
std::cout << "=== 值类别演示 ===" << std::endl;
int x = 10; // x是左值
int y = 20; // y是左值
// 左值引用
int& lref = x; // 绑定到左值
// int& lref2 = 10; // 错误!不能绑定到右值
// 右值引用
int&& rref = 10; // 绑定到右值
int&& rref2 = x + y; // 绑定到表达式结果(右值)
// int&& rref3 = x; // 错误!不能绑定到左值
std::cout << "左值引用: " << lref << std::endl;
std::cout << "右值引用: " << rref << std::endl;
std::cout << "表达式右值引用: " << rref2 << std::endl;
// 右值引用作为左值
int&& rref4 = 100;
int& lref3 = rref4; // 右值引用本身是左值!
std::cout << "右值引用作为左值: " << lref3 << std::endl;
}
static void stringExample() {
std::cout << "\n=== 字符串值类别 ===" << std::endl;
std::string str1 = "Hello";
std::string str2 = "World";
// 左值
std::string& lref = str1;
// 右值(临时对象)
std::string&& rref = str1 + str2;
std::string&& rref2 = std::string("Temporary");
std::cout << "左值引用: " << lref << std::endl;
std::cout << "右值引用: " << rref << std::endl;
std::cout << "临时对象右值引用: " << rref2 << std::endl;
}
};std::move和完美转发
cpp
#include <iostream>
#include <utility>
#include <string>
class MoveUtilities {
public:
static void moveDemo() {
std::cout << "=== std::move演示 ===" << std::endl;
std::string str = "Hello World";
std::cout << "移动前: " << str << std::endl;
// std::move将左值转换为右值引用
std::string moved_str = std::move(str);
std::cout << "移动后原字符串: '" << str << "'" << std::endl;
std::cout << "新字符串: " << moved_str << std::endl;
// 注意:std::move不执行移动,只是类型转换
// 实际移动由移动构造函数或移动赋值运算符执行
}
template<typename T>
static void forwardingFunction(T&& arg) {
// 完美转发:保持参数的值类别
processValue(std::forward<T>(arg));
}
static void processValue(const std::string& str) {
std::cout << "处理左值引用: " << str << std::endl;
}
static void processValue(std::string&& str) {
std::cout << "处理右值引用: " << str << std::endl;
}
static void perfectForwardingDemo() {
std::cout << "\n=== 完美转发演示 ===" << std::endl;
std::string str = "左值字符串";
std::cout << "直接调用:" << std::endl;
processValue(str); // 左值
processValue(std::string("右值")); // 右值
std::cout << "通过完美转发:" << std::endl;
forwardingFunction(str); // 转发左值
forwardingFunction(std::string("右值")); // 转发右值
}
};🏗️ 移动构造和移动赋值
实现移动语义
cpp
#include <iostream>
#include <cstring>
#include <algorithm>
class MyString {
private:
char* data_;
size_t size_;
public:
// 默认构造函数
MyString() : data_(nullptr), size_(0) {
std::cout << "默认构造函数" << std::endl;
}
// 构造函数
MyString(const char* str) {
size_ = strlen(str);
data_ = new char[size_ + 1];
strcpy(data_, str);
std::cout << "构造函数: " << data_ << std::endl;
}
// 拷贝构造函数
MyString(const MyString& other) {
size_ = other.size_;
data_ = new char[size_ + 1];
strcpy(data_, other.data_);
std::cout << "拷贝构造函数: " << data_ << std::endl;
}
// 移动构造函数
MyString(MyString&& other) noexcept
: data_(other.data_), size_(other.size_) {
// 将源对象置为有效但未指定状态
other.data_ = nullptr;
other.size_ = 0;
std::cout << "移动构造函数: " << data_ << std::endl;
}
// 拷贝赋值运算符
MyString& operator=(const MyString& other) {
if (this != &other) {
delete[] data_;
size_ = other.size_;
data_ = new char[size_ + 1];
strcpy(data_, other.data_);
std::cout << "拷贝赋值运算符: " << data_ << std::endl;
}
return *this;
}
// 移动赋值运算符
MyString& operator=(MyString&& other) noexcept {
if (this != &other) {
delete[] data_;
// 移动资源
data_ = other.data_;
size_ = other.size_;
// 重置源对象
other.data_ = nullptr;
other.size_ = 0;
std::cout << "移动赋值运算符: " << data_ << std::endl;
}
return *this;
}
// 析构函数
~MyString() {
if (data_) {
std::cout << "析构函数: " << data_ << std::endl;
delete[] data_;
} else {
std::cout << "析构函数: (空)" << std::endl;
}
}
// 工具函数
const char* c_str() const { return data_ ? data_ : ""; }
size_t size() const { return size_; }
bool empty() const { return size_ == 0; }
};
class MoveSemanticDemo {
public:
static void constructorDemo() {
std::cout << "=== 移动构造函数演示 ===" << std::endl;
MyString str1("Hello");
// 拷贝构造
MyString str2 = str1;
std::cout << "拷贝后 str1: " << str1.c_str() << std::endl;
std::cout << "拷贝后 str2: " << str2.c_str() << std::endl;
// 移动构造
MyString str3 = std::move(str1);
std::cout << "移动后 str1: " << str1.c_str() << std::endl;
std::cout << "移动后 str3: " << str3.c_str() << std::endl;
std::cout << std::endl;
}
static void assignmentDemo() {
std::cout << "=== 移动赋值运算符演示 ===" << std::endl;
MyString str1("World");
MyString str2("Hello");
std::cout << "赋值前 str1: " << str1.c_str() << std::endl;
std::cout << "赋值前 str2: " << str2.c_str() << std::endl;
// 拷贝赋值
str1 = str2;
std::cout << "拷贝赋值后 str1: " << str1.c_str() << std::endl;
std::cout << "拷贝赋值后 str2: " << str2.c_str() << std::endl;
// 移动赋值
MyString str3("Temporary");
str1 = std::move(str3);
std::cout << "移动赋值后 str1: " << str1.c_str() << std::endl;
std::cout << "移动赋值后 str3: " << str3.c_str() << std::endl;
std::cout << std::endl;
}
static void returnValueOptimization() {
std::cout << "=== 返回值优化演示 ===" << std::endl;
auto createString = [](const char* str) -> MyString {
return MyString(str); // RVO/NRVO可能会优化掉移动
};
MyString result = createString("RVO测试");
std::cout << "结果: " << result.c_str() << std::endl;
std::cout << std::endl;
}
};📦 容器中的移动语义
STL容器优化
cpp
#include <iostream>
#include <vector>
#include <string>
#include <chrono>
class ContainerMoveDemo {
public:
static void vectorMoveDemo() {
std::cout << "=== vector移动语义 ===" << std::endl;
std::vector<MyString> vec;
// 使用emplace_back避免临时对象
vec.emplace_back("String1");
vec.emplace_back("String2");
// 移动插入
MyString str("String3");
vec.push_back(std::move(str));
std::cout << "移动后原字符串: '" << str.c_str() << "'" << std::endl;
// 从函数返回移动
auto getString = []() -> MyString {
return MyString("ReturnValue");
};
vec.push_back(getString()); // 移动构造
std::cout << "vector大小: " << vec.size() << std::endl;
std::cout << std::endl;
}
static void performanceComparison() {
std::cout << "=== 性能比较 ===" << std::endl;
const size_t N = 100000;
// 测试拷贝性能
auto start = std::chrono::high_resolution_clock::now();
{
std::vector<std::string> vec;
for (size_t i = 0; i < N; ++i) {
std::string str = "String" + std::to_string(i);
vec.push_back(str); // 拷贝
}
}
auto end = std::chrono::high_resolution_clock::now();
auto copy_time = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
// 测试移动性能
start = std::chrono::high_resolution_clock::now();
{
std::vector<std::string> vec;
for (size_t i = 0; i < N; ++i) {
std::string str = "String" + std::to_string(i);
vec.push_back(std::move(str)); // 移动
}
}
end = std::chrono::high_resolution_clock::now();
auto move_time = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
// 测试emplace_back性能
start = std::chrono::high_resolution_clock::now();
{
std::vector<std::string> vec;
for (size_t i = 0; i < N; ++i) {
vec.emplace_back("String" + std::to_string(i)); // 就地构造
}
}
end = std::chrono::high_resolution_clock::now();
auto emplace_time = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "拷贝时间: " << copy_time.count() << "ms" << std::endl;
std::cout << "移动时间: " << move_time.count() << "ms" << std::endl;
std::cout << "emplace时间: " << emplace_time.count() << "ms" << std::endl;
}
};🔄 移动语义最佳实践
规则和指导原则
cpp
#include <iostream>
#include <memory>
#include <utility>
class MoveBestPractices {
public:
// Rule of Five 示例
class Resource {
private:
std::unique_ptr<int[]> data_;
size_t size_;
public:
// 1. 析构函数
~Resource() = default;
// 2. 拷贝构造函数
Resource(const Resource& other)
: size_(other.size_), data_(std::make_unique<int[]>(size_)) {
std::copy(other.data_.get(), other.data_.get() + size_, data_.get());
}
// 3. 拷贝赋值运算符
Resource& operator=(const Resource& other) {
if (this != &other) {
size_ = other.size_;
data_ = std::make_unique<int[]>(size_);
std::copy(other.data_.get(), other.data_.get() + size_, data_.get());
}
return *this;
}
// 4. 移动构造函数
Resource(Resource&& other) noexcept
: data_(std::move(other.data_)), size_(other.size_) {
other.size_ = 0;
}
// 5. 移动赋值运算符
Resource& operator=(Resource&& other) noexcept {
if (this != &other) {
data_ = std::move(other.data_);
size_ = other.size_;
other.size_ = 0;
}
return *this;
}
// 构造函数
explicit Resource(size_t size)
: size_(size), data_(std::make_unique<int[]>(size)) {
for (size_t i = 0; i < size_; ++i) {
data_[i] = static_cast<int>(i);
}
}
size_t size() const { return size_; }
int* data() const { return data_.get(); }
};
// 移动优先的函数设计
template<typename T>
static void processResource(T&& resource) {
// 完美转发
auto processed = std::forward<T>(resource);
std::cout << "处理资源,大小: " << processed.size() << std::endl;
}
static void demonstrateBestPractices() {
std::cout << "=== 移动语义最佳实践 ===" << std::endl;
// 1. 使用移动构造
Resource res1(1000);
Resource res2 = std::move(res1); // 移动构造
// 2. 使用移动赋值
Resource res3(500);
res3 = std::move(res2); // 移动赋值
// 3. 函数参数的完美转发
Resource res4(100);
processResource(res4); // 左值
processResource(Resource(200)); // 右值
// 4. 避免移动已移动的对象
Resource res5(300);
Resource res6 = std::move(res5);
// 不要再使用res5,除非重新赋值
// 5. noexcept的重要性
static_assert(std::is_nothrow_move_constructible_v<Resource>);
static_assert(std::is_nothrow_move_assignable_v<Resource>);
std::cout << "最佳实践演示完成" << std::endl;
}
// 移动语义在异常安全中的作用
static void exceptionSafetyDemo() {
std::cout << "\n=== 异常安全性 ===" << std::endl;
std::vector<Resource> vec;
// 如果移动构造函数是noexcept,vector会使用移动语义进行扩容
// 否则会使用拷贝构造以保证异常安全
for (int i = 0; i < 5; ++i) {
vec.emplace_back(100 * (i + 1));
std::cout << "vector大小: " << vec.size()
<< ", 容量: " << vec.capacity() << std::endl;
}
}
};
// 通用引用和引用折叠
template<typename T>
void universalReference(T&& param) {
// T&&是通用引用,不是右值引用
// 根据引用折叠规则:
// T& && -> T&
// T&& && -> T&&
if constexpr (std::is_lvalue_reference_v<T>) {
std::cout << "参数是左值引用" << std::endl;
} else {
std::cout << "参数是右值引用" << std::endl;
}
}
int main() {
ValueCategoryDemo::demonstrateCategories();
ValueCategoryDemo::stringExample();
MoveUtilities::moveDemo();
MoveUtilities::perfectForwardingDemo();
MoveSemanticDemo::constructorDemo();
MoveSemanticDemo::assignmentDemo();
MoveSemanticDemo::returnValueOptimization();
ContainerMoveDemo::vectorMoveDemo();
ContainerMoveDemo::performanceComparison();
MoveBestPractices::demonstrateBestPractices();
MoveBestPractices::exceptionSafetyDemo();
// 通用引用演示
std::cout << "\n=== 通用引用 ===" << std::endl;
int x = 10;
universalReference(x); // 左值
universalReference(20); // 右值
return 0;
}总结
移动语义是现代C++性能优化的重要特性,通过避免不必要的拷贝显著提升程序效率:
核心概念
- 右值引用:
T&&,绑定到临时对象 - 移动构造: 转移资源所有权而非拷贝
- 移动赋值: 高效的资源转移
- 完美转发: 保持参数的值类别
关键函数
| 函数 | 作用 | 注意事项 |
|---|---|---|
| std::move | 类型转换为右值引用 | 不执行移动操作 |
| std::forward | 完美转发 | 模板中使用 |
| 移动构造函数 | 转移资源 | 标记noexcept |
| 移动赋值运算符 | 转移资源 | 标记noexcept |
最佳实践
- Rule of Five: 五个特殊成员函数
- noexcept标记: 移动操作应该是noexcept
- 完美转发: 模板函数中保持值类别
- 避免移动已移动对象: 移动后对象处于有效但未指定状态
- 优先使用标准库: 如
std::unique_ptr
性能提升
- 避免深拷贝操作
- 减少内存分配
- 提升容器操作效率
- 优化函数返回值
设计原则
- 资源转移: 而非资源拷贝
- 异常安全: noexcept移动保证强异常安全
- 明确语义: 移动后对象状态清晰
- 性能优化: 在不改变语义的前提下提升性能
移动语义让C++在保持零开销抽象的同时,提供了更高效的资源管理方式。