挑战目标
深入理解现代C++的类设计原则、拷贝控制与移动语义。通过实现一个支持移动语义的自定义字符串类,你将掌握:
- Rule of Five(五大特殊成员函数)的正确实现
- 深拷贝与浅拷贝的区别与应用
- 移动语义的原理与性能优势
- 异常安全保证在资源管理类中的重要性
- 动态内存管理的正确模式
具体要求
基础功能要求
- 实现完整的
String类,支持动态内存管理与异常安全 - 实现五大特殊成员函数(Rule of Five):
- 默认构造函数
- 拷贝构造函数
- 移动构造函数
- 拷贝赋值运算符
- 移动赋值运算符
- 析构函数
- 支持常用字符串操作:
- 从C风格字符串构造
- 获取字符串长度
- 获取C风格字符串表示
- 字符串拼接操作
- 正确处理边界情况:
- 空字符串处理
- 自赋值检测
- 异常安全保证
接口设计参考
class String {
private:
char* data;
size_t length;
// 辅助函数:分配内存并拷贝字符串
void allocate_and_copy(const char* str, size_t len);
public:
// 1. 默认构造函数
String();
// 2. 从C字符串构造
explicit String(const char* str);
// 3. 拷贝构造函数
String(const String& other);
// 4. 移动构造函数
String(String&& other) noexcept;
// 5. 拷贝赋值运算符
String& operator=(const String& other);
// 6. 移动赋值运算符
String& operator=(String&& other) noexcept;
// 7. 析构函数
~String();
// 8. 获取长度
size_t size() const;
// 9. 获取C字符串
const char* c_str() const;
// 10. 字符串拼接
String operator+(const String& other) const;
};
难度分级
基础版(必需完成)
完成上述所有基础功能要求,并通过以下测试:
- 构造、拷贝、移动语义正确性
- 内存泄漏检测通过
- 基本字符串操作功能正常
进阶版(挑战提升)
在基础版基础上,增加以下功能:
- 实现拷贝交换习惯用法(Copy-and-Swap Idiom)
- 支持迭代器接口:
cpp
// 迭代器支持 char* begin(); const char* begin() const; char* end(); const char* end() const; - 实现更多字符串操作:
- 下标访问运算符
operator[] - 字符串比较操作符
operator==、operator!= - 子串查找功能
- 下标访问运算符
- 添加小字符串优化(SSO):对于短字符串(如长度≤15),直接存储在对象内部,避免堆分配
- 性能优化:使用移动语义优化字符串拼接操作
参考实现要点
1. 内存管理核心逻辑
cpp
void String::allocate_and_copy(const char* str, size_t len) {
data = new char[len + 1]; // 多分配一个字节存放'\0'
std::memcpy(data, str, len);
data[len] = '\0';
length = len;
}
2. 移动语义实现关键
cpp
// 移动构造函数
String::String(String&& other) noexcept
: data(other.data), length(other.length) {
// 转移资源所有权后,将源对象置为安全状态
other.data = nullptr;
other.length = 0;
}
// 移动赋值运算符
String& String::operator=(String&& other) noexcept {
if (this != &other) {
delete[] data; // 释放当前资源
data = other.data; // 接管资源
length = other.length;
other.data = nullptr; // 置空源对象
other.length = 0;
}
return *this;
}
3. 异常安全保证
- 基本异常安全:任何异常发生后,对象仍处于有效状态
- 强异常安全:拷贝赋值使用拷贝交换习惯用法
- 析构函数不抛出异常:确保资源正确释放
4. 拷贝交换习惯用法示例
cpp
// 友元交换函数
friend void swap(String& first, String& second) noexcept {
using std::swap;
swap(first.data, second.data);
swap(first.length, second.length);
}
// 使用拷贝交换的拷贝赋值运算符
String& operator=(const String& other) {
String temp(other); // 拷贝构造(可能抛出异常)
swap(*this, temp); // 交换资源(不抛出异常)
return *this; // temp离开作用域,释放旧资源
}
测试用例示例
基础测试用例
cpp
#include <cassert>
#include <cstring>
#include <utility>
void test_string_basic() {
// 测试1:默认构造
String s1;
assert(s1.size() == 0);
assert(std::strcmp(s1.c_str(), "") == 0);
// 测试2:C字符串构造
String s2("Hello");
assert(s2.size() == 5);
assert(std::strcmp(s2.c_str(), "Hello") == 0);
// 测试3:拷贝构造
String s3 = s2;
assert(s3.size() == 5);
assert(std::strcmp(s3.c_str(), "Hello") == 0);
// 验证深拷贝:修改s2不应影响s3
// (需要通过其他方式验证,如查看内存地址)
// 测试4:移动构造
String s4 = std::move(s2);
assert(s4.size() == 5);
assert(std::strcmp(s4.c_str(), "Hello") == 0);
// 验证移动后源对象状态
assert(s2.size() == 0);
assert(s2.c_str()[0] == '\0');
// 测试5:拷贝赋值
String s5;
s5 = s3;
assert(s5.size() == 5);
assert(std::strcmp(s5.c_str(), "Hello") == 0);
// 测试6:移动赋值
String s6("World");
s6 = std::move(s3);
assert(s6.size() == 5);
assert(std::strcmp(s6.c_str(), "Hello") == 0);
// 测试7:字符串拼接
String s7("Hello ");
String s8("World");
String s9 = s7 + s8;
assert(s9.size() == 11);
assert(std::strcmp(s9.c_str(), "Hello World") == 0);
// 测试8:自赋值安全性
s9 = s9;
assert(s9.size() == 11); // 自赋值后状态不变
// 测试9:空字符串处理
String s10("");
assert(s10.size() == 0);
String s11 = s10 + String("test");
assert(s11.size() == 4);
std::cout << "所有基础测试通过!" << std::endl;
}
内存泄漏检测建议
使用Valgrind或AddressSanitizer检查内存管理:
bash
# 使用Valgrind检测
valgrind --leak-check=full ./test_string
# 或使用gcc/clang的AddressSanitizer
g++ -fsanitize=address -g test_string.cpp -o test_string
扩展思考题
问题1:异常安全等级分析
你的String类实现了哪种异常安全保证?
- 无异常安全:异常可能导致资源泄漏或数据损坏
- 基本异常安全:异常后对象仍有效,但可能不是之前的状态
- 强异常安全:异常后对象状态完全不变
如何改进以达到强异常安全级别?
问题2:性能优化策略
比较以下两种字符串拼接实现的性能差异:
cpp
// 方案A:直接实现
String operator+(const String& other) const {
String result;
size_t new_len = length + other.length;
result.data = new char[new_len + 1];
if (data) std::memcpy(result.data, data, length);
if (other.data) std::memcpy(result.data + length, other.data, other.length);
result.data[new_len] = '\0';
result.length = new_len;
return result; // 可能触发移动构造
}
// 方案B:使用移动语义优化
String operator+(const String& other) && { // 右值引用限定符
// 如何优化?
}
如何为右值对象优化拼接操作,避免不必要的内存分配?
问题3:小字符串优化设计
设计小字符串优化(SSO)方案:
- 如何检测字符串长度决定存储策略?
- 对象内存布局如何设计?
- 如何保证接口一致性?
问题4:现代C++特性应用
如何用C++17/20特性改进String类?
- 使用
std::string_view作为参数类型 - 应用
constexpr和consteval - 使用三路比较运算符
<=>(C++20)
提交要求
- 完成String类的完整实现
- 编写测试用例验证所有功能
- 确保无内存泄漏(通过Valgrind检测)
- 记录实现过程中的关键决策与困难点
- 尝试回答至少2个扩展思考题
时间安排:建议1小时内完成基础版,额外时间挑战进阶功能。
验收标准:
- 所有基础测试用例通过
- Valgrind检测无内存泄漏
- 移动语义正确实现(源对象状态正确重置)
- 代码符合现代C++规范(Rule of Five)
相关资源
-
经典书籍参考:
- 《Effective C++》条款5-12:构造/析构/赋值操作
- 《Effective Modern C++》条款17:特殊成员函数生成
- 《C++ Primer》第13章:拷贝控制
-
在线文档:
-
代码示例:
src/week2/class_design.cpp- 类设计基础示例src/week2/string_impl.cpp- 字符串类参考实现
祝挑战顺利!通过这个练习,你将建立起面向对象设计的坚实基石。
6869



被折叠的 条评论
为什么被折叠?



