面试题答案
一键面试构造函数和普通函数在协同工作的特殊考量
- 内存管理:
- 构造函数:构造函数负责为对象分配资源,如使用
new
分配内存。在构造函数中分配的内存,通常期望在对象生命周期结束时由析构函数释放。如果构造函数中分配内存失败(如内存不足抛出异常),对象不应进入无效状态,且已分配的资源应正确释放。 - 普通函数:普通函数可以调用构造函数来创建对象。如果在普通函数中创建对象并调用其构造函数,当构造函数抛出异常时,普通函数需要正确处理异常,避免内存泄漏。普通函数也可能接受对象作为参数,对这些对象的资源管理应遵循对象自身的生命周期管理规则。
- 构造函数:构造函数负责为对象分配资源,如使用
- 异常安全性:
- 构造函数:构造函数必须保证“异常安全”,即如果在构造过程中抛出异常,对象应处于有效状态(通常是未构造成功的状态),并且已分配的资源应被正确释放。如果构造函数不能保证异常安全,可能会导致内存泄漏或对象处于不一致状态。
- 普通函数:普通函数在调用构造函数创建对象时,需要处理可能抛出的异常。如果不处理,异常会向上层调用栈传播,可能导致程序崩溃。普通函数自身在执行过程中也可能抛出异常,需要考虑对调用者的影响,以及如何与构造函数的异常处理协同工作。
确保内存不泄漏的代码设计
在C++中,为了确保在构造函数分配内存并可能抛出异常时不发生内存泄漏,可以使用智能指针。智能指针会自动管理所指向对象的生命周期,当智能指针离开作用域时,会自动释放其所指向的内存。
以下是一个完整的代码示例:
#include <iostream>
#include <memory>
class MyClass {
private:
std::unique_ptr<int[]> data;
public:
MyClass(int size) {
try {
data = std::make_unique<int[]>(size);
// 模拟可能抛出异常的操作
if (size > 10) {
throw std::runtime_error("Size too large");
}
// 初始化数组
for (int i = 0; i < size; ++i) {
data[i] = i;
}
} catch (const std::exception& e) {
std::cerr << "Exception in constructor: " << e.what() << std::endl;
// 这里智能指针data会自动释放内存,无需手动处理
}
}
// 析构函数无需手动释放data,因为unique_ptr会自动处理
~MyClass() = default;
};
int main() {
try {
MyClass obj(15);
} catch (const std::exception& e) {
std::cerr << "Caught in main: " << e.what() << std::endl;
}
return 0;
}
代码解释
- 类定义:
MyClass
类中使用std::unique_ptr<int[]>
来管理动态分配的整数数组。std::unique_ptr
是C++标准库提供的智能指针,它负责自动释放所指向的内存。
- 构造函数:
- 使用
std::make_unique<int[]>(size)
分配一个大小为size
的整数数组,并将其赋值给data
。 - 模拟一个可能抛出异常的条件,如果
size
大于10,抛出std::runtime_error
异常。 - 如果在构造函数执行过程中抛出异常,由于
data
是std::unique_ptr
类型,它会自动释放分配的内存,从而避免内存泄漏。
- 使用
- 析构函数:
- 由于使用了
std::unique_ptr
,析构函数可以使用默认实现,std::unique_ptr
会在对象销毁时自动释放内存。
- 由于使用了
- main函数:
- 在
main
函数中尝试创建MyClass
对象,并传入size
为15,这会导致构造函数抛出异常。 try - catch
块捕获并处理异常,输出异常信息。在这个过程中,内存不会泄漏,因为MyClass
构造函数中的智能指针管理了内存释放。
- 在