如何在C++中结合Move semantics与this指针实现高效不可变对象?
结合移动语义实现高效不可变字符串对象
要实现你的需求,核心是利用C++11引入的成员函数引用限定符,它能让编译器根据调用对象的左值/右值属性自动选择对应的成员函数版本。以下是具体实现思路和修正后的代码:
核心思路
- 给
append方法添加引用限定符,区分两种调用场景:- 左值对象(具名变量,如
a)调用const &限定的append,返回新创建的字符串,保证原对象不可变 - 右值对象(临时对象、被
std::move转换的对象)调用&&限定的append,直接修改当前对象并返回它,利用移动语义避免不必要的拷贝
- 左值对象(具名变量,如
修正后的完整代码
#include <iostream> #include <cstring> #include <utility> // 用于std::move class string { char* data; // 辅助函数:获取字符串长度,避免重复调用strlen size_t size() const { return data ? std::strlen(data) : 0; } public: // 构造函数 string(const char* p) { size_t len = std::strlen(p) + 1; data = new char[len]; std::memcpy(data, p, len); } // 析构函数 ~string() { delete[] data; } // 拷贝构造 string(const string& that) { size_t len = that.size() + 1; data = new char[len]; std::memcpy(data, that.data, len); } // 移动构造 string(string&& that) noexcept : data(that.data) { that.data = nullptr; } // 左值对象调用的append:返回新对象,不修改原对象 string append(const char* p) const & { std::cout << "copy" << std::endl; size_t total_len = size() + std::strlen(p) + 1; char* new_data = new char[total_len]; std::strcpy(new_data, data); std::strcat(new_data, p); return string(new_data); // 利用私有构造优化内存分配 } // 右值对象调用的append:修改当前对象,返回自身的右值引用 string&& append(const char* p) && { std::cout << "move" << std::endl; size_t total_len = size() + std::strlen(p) + 1; char* new_data = new char[total_len]; std::strcpy(new_data, data); std::strcat(new_data, p); delete[] data; data = new_data; return std::move(*this); } private: // 私有构造:直接接收已分配的char*,避免重复分配内存 explicit string(char* new_data) : data(new_data) {} }; int main() { string a("a"); string b = a.append("..."); // 调用copy版本 string c = a.append("1...").append("2..."); // 先copy,再move string d = string("s").append("1...").append("2..."); // 两次move return 0; }
关键细节说明
- 成员函数引用限定符:
const &限定:仅左值对象可调用,保证原对象不被修改,符合不可变性要求&&限定:仅右值对象可调用,允许修改当前对象,因为右值对象后续不会再被使用
- 右值版本返回值:返回
string&&而非string&,确保链式调用时仍能识别为右值,继续使用移动版本 - 私有构造优化:新增的
string(char*)构造函数避免临时对象的二次内存分配,提升效率 - noexcept移动构造:标记移动构造为
noexcept,让编译器更安全地优先使用移动语义而非拷贝
预期输出
运行代码后,控制台将输出:
copy copy move move move
完全符合你预期的调用逻辑。
内容的提问来源于stack exchange,提问作者Jean-Luc Delarbre
相关产品推荐
相关产品推荐

