面试题答案
一键面试-
拷贝构造函数和赋值运算符重载函数调用分析:
vec.push_back(obj1)
:- 当执行
vec.push_back(obj1)
时,由于std::vector
在初始为空时,push_back
会在容器末尾添加新元素。因为MyType
对象obj1
是右值(在这种情况下),std::vector
会调用MyType
的拷贝构造函数来构造新的元素放入vector
中。所以这里会调用一次MyType
的拷贝构造函数。
- 当执行
vec.insert(vec.begin(), obj2)
:- 当执行
vec.insert(vec.begin(), obj2)
时,std::vector
需要在begin()
位置插入新元素。如果vector
当前容量不足,会发生重新分配内存的情况。 - 首先,
obj2
会通过拷贝构造函数被复制到新的内存位置(因为insert
操作需要创建一个新的元素在指定位置),这是一次拷贝构造函数调用。 - 然后,原来
vec[0]
的元素需要移动到新的位置(如果发生了重新分配内存),这会调用MyType
的拷贝构造函数来构造新位置的元素。假设没有重新分配内存,由于insert
操作在begin()
位置插入,obj1
需要向后移动一个位置,这会调用MyType
的移动构造函数(如果存在),如果没有移动构造函数则调用拷贝构造函数。在上述代码没有移动构造函数的情况下,会再次调用MyType
的拷贝构造函数。
- 当执行
-
包含资源(如文件句柄)可能出现的问题:
- 资源重复释放:
- 当
MyType
对象被拷贝(通过拷贝构造函数)时,新的对象和原对象都会持有相同的资源(如文件句柄)。当其中一个对象析构时,会释放该资源,导致另一个对象持有无效的资源句柄,后续使用该对象就会产生未定义行为,例如访问已关闭的文件句柄。
- 当
- 内存泄漏:
- 如果在
vector
重新分配内存时,旧的对象被析构(因为内存重新分配),而新的对象没有正确获取资源,就会导致资源没有被正确释放,从而造成内存泄漏。
- 如果在
- 资源重复释放:
-
解决方案:
- 采用RAII(Resource Acquisition Is Initialization)原则:
- 移动语义:
- 在
MyType
类中添加移动构造函数和移动赋值运算符重载。移动构造函数将资源从源对象“窃取”而不是拷贝,这样可以避免资源重复释放和内存泄漏问题。
class MyType { private: int value; // 假设这里有一个文件句柄 FILE* fileHandle; public: MyType(int v) : value(v) { fileHandle = fopen("test.txt", "w"); if (!fileHandle) { // 处理文件打开失败的情况 throw std::runtime_error("Failed to open file"); } } MyType(const MyType& other) : value(other.value) { fileHandle = fopen("test.txt", "w"); if (!fileHandle) { // 处理文件打开失败的情况 throw std::runtime_error("Failed to open file"); } // 这里简单复制文件内容,实际应用中可能更复杂 // 从other.fileHandle读取内容写入fileHandle } MyType& operator=(const MyType& other) { if (this != &other) { fclose(fileHandle); value = other.value; fileHandle = fopen("test.txt", "w"); if (!fileHandle) { // 处理文件打开失败的情况 throw std::runtime_error("Failed to open file"); } // 这里简单复制文件内容,实际应用中可能更复杂 // 从other.fileHandle读取内容写入fileHandle } return *this; } MyType(MyType&& other) noexcept : value(other.value), fileHandle(other.fileHandle) { other.fileHandle = nullptr; } MyType& operator=(MyType&& other) noexcept { if (this != &other) { fclose(fileHandle); value = other.value; fileHandle = other.fileHandle; other.fileHandle = nullptr; } return *this; } ~MyType() { if (fileHandle) { fclose(fileHandle); } } };
- 在
- 智能指针:
- 使用智能指针(如
std::unique_ptr
或std::shared_ptr
)来管理资源。例如,如果MyType
类中的资源是一个动态分配的对象,可以使用std::unique_ptr
来管理它。
class Resource { public: // 假设Resource类有一些操作 void doSomething() { // 具体操作 } }; class MyType { private: int value; std::unique_ptr<Resource> resourcePtr; public: MyType(int v) : value(v), resourcePtr(std::make_unique<Resource>()) {} MyType(const MyType& other) : value(other.value), resourcePtr(std::make_unique<Resource>()) { // 可以根据需要从other.resourcePtr复制数据到resourcePtr } MyType& operator=(const MyType& other) { if (this != &other) { value = other.value; resourcePtr = std::make_unique<Resource>(); // 可以根据需要从other.resourcePtr复制数据到resourcePtr } return *this; } // 移动构造函数和移动赋值运算符重载自动生成,因为使用了std::unique_ptr };
- 如果资源需要被多个
MyType
对象共享,可以使用std::shared_ptr
。std::shared_ptr
使用引用计数来管理资源的生命周期,当最后一个指向资源的std::shared_ptr
被销毁时,资源才会被释放。
class Resource { public: // 假设Resource类有一些操作 void doSomething() { // 具体操作 } }; class MyType { private: int value; std::shared_ptr<Resource> resourcePtr; public: MyType(int v) : value(v), resourcePtr(std::make_shared<Resource>()) {} MyType(const MyType& other) : value(other.value), resourcePtr(other.resourcePtr) {} MyType& operator=(const MyType& other) { if (this != &other) { value = other.value; resourcePtr = other.resourcePtr; } return *this; } // 移动构造函数和移动赋值运算符重载自动生成,因为使用了std::shared_ptr };
- 使用智能指针(如
- 移动语义:
- 采用RAII(Resource Acquisition Is Initialization)原则: