面试题答案
一键面试异常处理对RAII的影响
- 正常情况:RAII(Resource Acquisition Is Initialization)通过对象的构造和析构来管理资源,当对象离开其作用域时,析构函数自动调用以释放资源。在没有异常的情况下,RAII能很好地工作。
- 异常情况:当异常发生时,程序的控制流会发生改变。如果在RAII对象的构造函数和析构函数之间抛出异常,RAII机制仍能保证资源的正确释放,因为栈展开(stack unwinding)会调用已构造对象的析构函数。
示例 - 文件描述符管理
#include <iostream>
#include <fstream>
class FileRAII {
public:
FileRAII(const std::string& filename) : file(filename) {
if (!file) {
throw std::runtime_error("Failed to open file");
}
}
~FileRAII() {
if (file.is_open()) {
file.close();
}
}
std::ofstream& getFile() {
return file;
}
private:
std::ofstream file;
};
void writeToFile() {
FileRAII file("test.txt");
// 这里可能抛出异常
file.getFile() << "Some data" << std::endl;
}
int main() {
try {
writeToFile();
} catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}
在上述例子中,如果在writeToFile
函数中抛出异常,FileRAII
对象的析构函数会被调用,确保文件被正确关闭。
示例 - 锁资源管理
#include <iostream>
#include <mutex>
class LockRAII {
public:
LockRAII(std::mutex& mtx) : mutex(mtx) {
mutex.lock();
}
~LockRAII() {
mutex.unlock();
}
private:
std::mutex& mutex;
};
std::mutex globalMutex;
void criticalSection() {
LockRAII lock(globalMutex);
// 这里可能抛出异常
std::cout << "In critical section" << std::endl;
}
int main() {
try {
criticalSection();
} catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}
在这个例子中,如果criticalSection
函数中抛出异常,LockRAII
对象的析构函数会解锁互斥锁,防止死锁。
处理不当导致的资源泄漏
- 未正确实现析构函数:如果RAII对象的析构函数没有正确编写,例如在释放文件描述符或锁资源时发生错误,资源可能无法正确释放。例如:
class BadFileRAII {
public:
BadFileRAII(const std::string& filename) : file(filename) {
if (!file) {
throw std::runtime_error("Failed to open file");
}
}
~BadFileRAII() {
// 这里忘记关闭文件
}
private:
std::ofstream file;
};
- 异常安全问题:如果RAII对象的构造函数抛出异常,但之前已经获取了部分资源,而没有相应的清理机制,也会导致资源泄漏。例如:
class ComplexResourceRAII {
public:
ComplexResourceRAII() {
resource1 = acquireResource1();
if (!resource1) {
throw std::runtime_error("Failed to acquire resource1");
}
resource2 = acquireResource2();
if (!resource2) {
// 这里没有释放resource1
throw std::runtime_error("Failed to acquire resource2");
}
}
~ComplexResourceRAII() {
releaseResource2(resource2);
releaseResource1(resource1);
}
private:
ResourceType1 resource1;
ResourceType2 resource2;
ResourceType1 acquireResource1();
ResourceType2 acquireResource2();
void releaseResource1(ResourceType1 res);
void releaseResource2(ResourceType2 res);
};
在上述例子中,如果acquireResource2
抛出异常,resource1
没有被释放,导致资源泄漏。为避免这种情况,可以在构造函数中使用嵌套的RAII对象来管理每个资源。