面试题答案
一键面试std::unique_ptr
- 特点:
- 独占式拥有对象,同一时刻只能有一个
std::unique_ptr
指向给定对象。 - 不能复制,只能移动,移动语义高效,避免了不必要的资源复制。
- 析构时自动释放所管理的对象。
- 独占式拥有对象,同一时刻只能有一个
- 适用场景:
- 当一个对象在程序的某个特定部分有明确的单一所有者时使用,比如函数内部创建的临时对象。
- 管理动态分配的数组可以使用
std::unique_ptr<T[]>
。
- 预防内存泄漏方式:
- 超出作用域时,
std::unique_ptr
的析构函数自动调用delete
释放对象,无需手动管理内存。
- 超出作用域时,
示例代码:
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass created" << std::endl; }
~MyClass() { std::cout << "MyClass destroyed" << std::endl; }
};
void testUniquePtr() {
std::unique_ptr<MyClass> ptr(new MyClass());
// 这里无需手动delete ptr,ptr离开作用域时会自动释放MyClass对象
}
int main() {
testUniquePtr();
return 0;
}
std::shared_ptr
- 特点:
- 允许多个
std::shared_ptr
指向同一个对象,通过引用计数来管理对象的生命周期。 - 支持复制和赋值操作,每次复制或赋值,引用计数增加;
std::shared_ptr
析构时,引用计数减少。 - 当引用计数为0时,自动释放所管理的对象。
- 允许多个
- 适用场景:
- 当多个对象需要共享同一资源时使用,比如在多个模块间共享数据。
- 预防内存泄漏方式:
- 引用计数机制确保只有当所有指向对象的
std::shared_ptr
都销毁后,对象才会被释放,避免了手动管理不当导致的内存泄漏。
- 引用计数机制确保只有当所有指向对象的
示例代码:
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass created" << std::endl; }
~MyClass() { std::cout << "MyClass destroyed" << std::endl; }
};
void testSharedPtr() {
std::shared_ptr<MyClass> ptr1(new MyClass());
std::shared_ptr<MyClass> ptr2 = ptr1; // 引用计数增加
{
std::shared_ptr<MyClass> ptr3 = ptr1; // 引用计数再增加
} // ptr3离开作用域,引用计数减少
// 当ptr1和ptr2都离开作用域时,MyClass对象才会被释放
}
int main() {
testSharedPtr();
return 0;
}
std::weak_ptr
- 特点:
- 弱引用指针,指向由
std::shared_ptr
管理的对象,但不增加引用计数。 - 主要用于解决
std::shared_ptr
的循环引用问题。 - 可以通过
lock()
方法尝试获取指向对象的std::shared_ptr
,如果对象已被释放,lock()
返回一个空的std::shared_ptr
。
- 弱引用指针,指向由
- 适用场景:
- 在对象之间存在循环引用时使用,打破循环引用,避免内存泄漏。
- 预防内存泄漏方式:
- 不增加引用计数,当循环引用中的对象只被
std::weak_ptr
指向时,不会阻止对象被释放,从而打破循环引用,预防内存泄漏。
- 不增加引用计数,当循环引用中的对象只被
示例代码:
#include <iostream>
#include <memory>
class B;
class A {
public:
std::weak_ptr<B> bPtr;
~A() { std::cout << "A destroyed" << std::endl; }
};
class B {
public:
std::weak_ptr<A> aPtr;
~B() { std::cout << "B destroyed" << std::endl; }
};
void testWeakPtr() {
std::shared_ptr<A> a(new A());
std::shared_ptr<B> b(new B());
a->bPtr = b;
b->aPtr = a;
// 这里不会出现循环引用导致的内存泄漏
}
int main() {
testWeakPtr();
return 0;
}
复杂对象关系(循环引用)处理方式
在使用 std::shared_ptr
时,如果对象之间存在循环引用,会导致引用计数永远不会为0,从而造成内存泄漏。例如:
#include <iostream>
#include <memory>
class B;
class A {
public:
std::shared_ptr<B> bPtr;
~A() { std::cout << "A destroyed" << std::endl; }
};
class B {
public:
std::shared_ptr<A> aPtr;
~B() { std::cout << "B destroyed" << std::endl; }
};
void testLoopReference() {
std::shared_ptr<A> a(new A());
std::shared_ptr<B> b(new B());
a->bPtr = b;
b->aPtr = a;
// a和b的引用计数都不会变为0,A和B对象不会被释放,造成内存泄漏
}
int main() {
testLoopReference();
return 0;
}
解决循环引用问题可以使用 std::weak_ptr
,将其中一个类的成员指针改为 std::weak_ptr
,如上述 testWeakPtr
示例代码,这样就打破了循环引用,对象能够正常释放,避免内存泄漏。