在C++中,当类涉及动态内存分配时,需特别注意资源管理,以避免内存泄漏、悬空指针等问题。以下是关键点和示例代码:
核心原则
- 析构函数:负责释放动态分配的内存。
- 拷贝构造函数:实现深拷贝,复制数据而非指针。
- 拷贝赋值运算符:处理自赋值并实现深拷贝。
- 移动语义(C++11+):通过移动构造函数和移动赋值运算符提升性能。
示例:自定义字符串类
cpp
#include <cstring>
#include <utility>
class MyString {
private:
char* str;
public:
// 构造函数
explicit MyString(const char* s = "") {
str = new char[std::strlen(s) + 1];
std::strcpy(str, s);
}
// 析构函数
~MyString() {
delete[] str;
}
// 拷贝构造函数(深拷贝)
MyString(const MyString& other) {
str = new char[std::strlen(other.str) + 1];
std::strcpy(str, other.str);
}
// 拷贝赋值运算符(拷贝并交换)
MyString& operator=(MyString other) {
swap(other);
return *this;
}
// 移动构造函数
MyString(MyString&& other) noexcept : str(other.str) {
other.str = nullptr;
}
// 移动赋值运算符
MyString& operator=(MyString&& other) noexcept {
if (this != &other) {
delete[] str;
str = other.str;
other.str = nullptr;
}
return *this;
}
// 交换辅助函数
void swap(MyString& other) noexcept {
std::swap(str, other.str);
}
// 访问数据
const char* c_str() const {
return str;
}
};
关键点解析
-
深拷贝与浅拷贝:
- 默认拷贝操作复制指针值(浅拷贝),导致多个对象共享同一内存。
- 需手动实现拷贝构造函数和赋值运算符,分配新内存并复制内容(深拷贝)。
-
拷贝并交换惯用法:
- 赋值运算符通过传值调用拷贝构造函数生成临时对象。
- 交换当前对象与临时对象的资源,临时对象析构时自动释放旧资源。
-
移动语义:
- 移动操作"窃取"资源,避免不必要的深拷贝。
- 移动后源对象置于有效但未定义状态(通常置空指针)。
-
异常安全:
- 在分配新内存前不修改原对象状态,确保异常时对象仍有效。
使用智能指针简化管理
若使用 std::unique_ptr
管理资源,可减少手动内存管理,但需显式实现深拷贝:
cpp
#include <memory>
#include <algorithm>
class MyArray {
private:
std::unique_ptr<int[]> arr;
size_t size;
public:
MyArray(size_t s) : arr(std::make_unique<int[]>(s)), size(s) {}
// 深拷贝构造函数
MyArray(const MyArray& other) : arr(std::make_unique<int[]>(other.size)), size(other.size) {
std::copy(&other.arr[0], &other.arr[size], &arr[0]);
}
// 拷贝赋值运算符(拷贝并交换)
MyArray& operator=(MyArray other) {
swap(other);
return *this;
}
// 移动操作由 unique_ptr 自动处理
MyArray(MyArray&&) noexcept = default;
MyArray& operator=(MyArray&&) noexcept = default;
void swap(MyArray& other) noexcept {
std::swap(arr, other.arr);
std::swap(size, other.size);
}
};
总结
- 手动管理内存时,必须实现析构函数、拷贝构造、拷贝赋值(移动操作可选但推荐)。
- 使用智能指针 (如
std::unique_ptr
)可自动管理释放,但深拷贝仍需手动实现。 - 遵循规则:若定义了拷贝构造、拷贝赋值或析构函数,通常需同时考虑移动操作。
正确管理动态内存能有效避免资源泄漏和程序崩溃,是C++高效编程的关键技能。