C++构造函数中使用new的基础场景
在C++中,当我们需要在对象生命周期内持有动态分配的资源时,经常会在构造函数中使用new来申请内存。比如下面这个简单的字符串类,在构造时通过new分配字符数组空间:

#include <cstring>
#include <iostream>
class MyString {
private:
char* data;
int length;
public:
// 构造函数中使用new分配内存
MyString(const char* str = nullptr) {
if (str == nullptr) {
length = 0;
data = new char[1];
data[0] = ' ';
} else {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
}
}
};
核心注意事项一:必须配套实现析构函数释放内存
构造函数中用new申请的内存,必须在对象销毁时释放,否则会造成内存泄漏。因此需要显式定义析构函数,在其中使用delete或者delete[]释放对应的资源:
~MyString() {
if (data != nullptr) {
delete[] data; // 注意数组要用delete[],单个对象用delete
data = nullptr;
}
}
这里要注意,如果构造函数中使用的是new type[]申请数组,析构必须用delete[];如果是new type申请单个对象,析构用delete,二者不能混用,否则会导致未定义行为。
核心注意事项二:实现正确的拷贝构造函数
如果类中有动态分配的资源,编译器默认生成的拷贝构造函数只会做浅拷贝,也就是把指针的值复制过去,不会复制指针指向的内存。这样会导致两个对象的指针指向同一块内存,销毁时会被释放两次,引发程序崩溃:
// 错误的浅拷贝示例,编译器默认生成的拷贝构造就是这种逻辑
MyString s1("hello");
MyString s2 = s1; // 此时s1.data和s2.data指向同一块内存
// 当s1和s2销毁时,同一块内存会被delete[]两次,程序崩溃
因此需要自己实现深拷贝的拷贝构造函数,为新对象分配独立的内存空间,复制原对象的内容:
// 深拷贝拷贝构造函数
MyString(const MyString& other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
}
核心注意事项三:重载赋值运算符实现深拷贝
和拷贝构造类似,默认的赋值运算符也是浅拷贝,同样会引发重复释放的问题。而且赋值操作还需要处理自赋值的情况,以及先释放旧资源再分配新资源,避免内存泄漏:
// 重载赋值运算符
MyString& operator=(const MyString& other) {
// 处理自赋值情况,避免先释放自己的内存再拷贝导致错误
if (this == &other) {
return *this;
}
// 先释放当前对象的旧资源
delete[] data;
// 分配新资源并拷贝内容
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
return *this;
}
核心注意事项四:异常安全处理
如果new申请内存失败,会抛出std::bad_alloc异常。如果构造函数中申请了多个资源,前面的资源申请成功,后面的申请失败,需要保证已经申请的资源能被正确释放,避免内存泄漏:
class ResourceHolder {
private:
int* res1;
int* res2;
public:
ResourceHolder() {
res1 = new int(10); // 先申请res1
try {
res2 = new int(20); // 再申请res2,如果这里失败会抛异常
} catch (...) {
delete res1; // 捕获异常,释放已经申请的res1,避免泄漏
throw; // 重新抛出异常
}
}
~ResourceHolder() {
delete res1;
delete res2;
}
};
常见错误总结
- 忘记实现析构函数,导致动态分配的内存无法释放,造成内存泄漏
- 拷贝构造和赋值运算符没有实现深拷贝,导致多个对象共享同一块内存,重复释放崩溃
- new和delete、new[]和delete[]混用,引发未定义行为
- 赋值运算符没有处理自赋值场景,导致先释放自己的内存再拷贝出错
- 构造函数中申请多个资源时没有做异常安全处理,部分资源申请失败时泄漏已申请资源
简化方案:使用智能指针管理资源
如果觉得手动管理内存容易出错,可以使用C++11引入的智能指针,比如std::unique_ptr或者std::shared_ptr来管理动态资源,智能指针会在对象销毁时自动释放管理的资源,不需要手动写析构函数,也能避免很多拷贝相关的问题:
#include <memory>
#include <cstring>
class SafeString {
private:
std::unique_ptr<char[]> data;
int length;
public:
SafeString(const char* str = nullptr) {
if (str == nullptr) {
length = 0;
data = std::make_unique<char[]>(1);
data[0] = ' ';
} else {
length = strlen(str);
data = std::make_unique<char[]>(length + 1);
strcpy(data.get(), str);
}
}
// 不需要手动写析构函数、拷贝构造、赋值运算符,unique_ptr会自动处理
};