面试题答案
一键面试1. 智能指针实现原理
unique_ptr
- 原理:
unique_ptr
采用了一种独占式拥有的策略,它持有对对象的唯一所有权。当unique_ptr
被销毁时,它所指向的对象也会被自动销毁。这是通过在unique_ptr
内部维护一个指向动态分配对象的指针,并在unique_ptr
的析构函数中释放该对象来实现的。 - 实现示例(简化):
template <typename T>
class MyUniquePtr {
T* ptr;
public:
MyUniquePtr(T* p = nullptr) : ptr(p) {}
~MyUniquePtr() {
if (ptr) {
delete ptr;
}
}
T& operator*() { return *ptr; }
T* operator->() { return ptr; }
// 移动构造函数和移动赋值运算符
MyUniquePtr(MyUniquePtr&& other) noexcept : ptr(other.ptr) {
other.ptr = nullptr;
}
MyUniquePtr& operator=(MyUniquePtr&& other) noexcept {
if (this != &other) {
delete ptr;
ptr = other.ptr;
other.ptr = nullptr;
}
return *this;
}
// 禁止拷贝构造函数和拷贝赋值运算符
MyUniquePtr(const MyUniquePtr&) = delete;
MyUniquePtr& operator=(const MyUniquePtr&) = delete;
};
shared_ptr
- 原理:
shared_ptr
采用引用计数机制来管理对象的生命周期。多个shared_ptr
可以指向同一个对象,当指向该对象的最后一个shared_ptr
被销毁时,对象才会被释放。它内部包含两个指针,一个指向对象,另一个指向一个控制块,控制块中包含引用计数以及可能的其他信息(如删除器)。 - 实现示例(简化):
template <typename T>
class MySharedPtr {
T* ptr;
long* refCount;
public:
MySharedPtr(T* p = nullptr) : ptr(p) {
if (ptr) {
refCount = new long(1);
} else {
refCount = new long(0);
}
}
MySharedPtr(const MySharedPtr& other) : ptr(other.ptr), refCount(other.refCount) {
++(*refCount);
}
MySharedPtr& operator=(const MySharedPtr& other) {
if (this != &other) {
release();
ptr = other.ptr;
refCount = other.refCount;
++(*refCount);
}
return *this;
}
~MySharedPtr() {
release();
}
T& operator*() { return *ptr; }
T* operator->() { return ptr; }
private:
void release() {
if (--(*refCount) == 0) {
delete ptr;
delete refCount;
}
}
};
weak_ptr
- 原理:
weak_ptr
是一种弱引用,它不增加对象的引用计数,主要用于解决shared_ptr
中的循环引用问题。weak_ptr
可以指向由shared_ptr
管理的对象,但它不会影响对象的生命周期。通过weak_ptr
可以获取一个shared_ptr
,如果对象已经被销毁,获取的shared_ptr
将为空。 - 实现示例(简化):
template <typename T>
class MyWeakPtr {
T* ptr;
long* refCount;
public:
MyWeakPtr() : ptr(nullptr), refCount(nullptr) {}
MyWeakPtr(const MySharedPtr<T>& sharedPtr) : ptr(sharedPtr.ptr), refCount(sharedPtr.refCount) {}
MySharedPtr<T> lock() const {
if (ptr && refCount && *refCount > 0) {
return MySharedPtr<T>(*this);
}
return MySharedPtr<T>();
}
};
2. 引用计数机制在智能指针中的应用
- shared_ptr:引用计数是
shared_ptr
的核心机制。每次创建一个新的shared_ptr
指向对象时,引用计数加1;当shared_ptr
被销毁或赋值给另一个对象时,引用计数减1。当引用计数变为0时,对象被释放。 - weak_ptr:虽然
weak_ptr
本身不直接参与引用计数,但它可以观察shared_ptr
管理的对象的引用计数。通过lock
方法,weak_ptr
可以尝试获取一个shared_ptr
,如果对象的引用计数大于0,则获取成功,否则获取失败(返回空的shared_ptr
)。
3. 在复杂内存管理场景下合理使用智能指针
循环引用
- 问题描述:当两个或多个对象通过
shared_ptr
相互引用时,会形成循环引用,导致引用计数永远不会为0,从而造成内存泄漏。 - 解决方法:使用
weak_ptr
打破循环引用。 - 示例:
class B;
class A {
public:
std::shared_ptr<B> ptrB;
~A() { std::cout << "A destroyed" << std::endl; }
};
class B {
public:
std::weak_ptr<A> ptrA;
~B() { std::cout << "B destroyed" << std::endl; }
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->ptrB = b;
b->ptrA = a;
// 此时 a 和 b 不会被销毁,因为存在循环引用
// 但如果 B 中的 ptrA 是 weak_ptr,就可以打破循环
return 0;
}
当离开 main
函数作用域时,如果 B
中的 ptrA
是 weak_ptr
,那么 a
和 b
的引用计数会正常减少,最终对象 A
和 B
会被正确销毁。
对象生命周期管理
- 问题描述:在复杂的系统中,对象之间的依赖关系复杂,手动管理对象的生命周期容易出错,导致内存泄漏或悬空指针。
- 解决方法:使用智能指针来自动管理对象的生命周期。例如,在函数返回对象时,可以返回
unique_ptr
或shared_ptr
,确保对象在不再需要时被正确释放。 - 示例:
std::unique_ptr<int> createInt() {
return std::make_unique<int>(42);
}
int main() {
auto ptr = createInt();
// 当 ptr 离开作用域时,它所指向的 int 对象会被自动释放
return 0;
}
在这个例子中,createInt
函数返回一个 unique_ptr<int>
,调用者 main
函数接收这个 unique_ptr
,当 ptr
离开 main
函数作用域时,int
对象会被自动释放,避免了内存泄漏。同时,由于 unique_ptr
的独占性,也避免了悬空指针问题(因为不存在其他指针指向该对象)。如果需要多个地方共享这个对象,可以使用 shared_ptr
。