Skip to content

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++在保持零开销抽象的同时,提供了更高效的资源管理方式。

本站内容仅供学习和研究使用。