智能指针

智能指针

unique_ptr

作用域指针,不能复制

栈分配指针,当死亡时,自动释放所管理的内存,无需显示调用delete

1
2
3
std::unique_ptr<int> p1 = std::make_unique<int>(10); // 管理动态分配的内存
// std::unique_ptr<int> p2 = p1; // 错误!unique_ptr不支持拷贝
std::unique_ptr<int> p2 = std::move(p1); // 通过 std::move 转移所有权

只能显示调用构造函数,因为其构造函数有explicit关键字,没有了构造函数的隐式转换

最好的调用还是使用make_unique会捕获异常,不会产生悬空指针问题

shared_ptr

追踪引用计数,如果引用为0,则释放内存

需要分配内存用于计数

1
shared_ptr<Entity> p = make_shared<Entity>();

作用主要包括:

  1. 自动管理动态分配的对象,避免手动调用 delete
  2. 支持共享所有权,让多个 shared_ptr 可以安全地访问同一对象。
  3. 借助引用计数机制,实现对象的生命周期控制,当最后一个 shared_ptr 销毁后,自行释放资源。

unique_ptr:独占所有权,不支持多个指针管理同一个对象;更轻量且不存在循环引用问题。

weak_ptr:用于观察 shared_ptr 管理的对象,不增加引用计数;主要用于辅助 shared_ptr,避免循环引用的问题。

std::weak_ptr

std::weak_ptr 是 C++11 引入的一种智能指针,和 std::shared_ptr 一起使用,用于避免 循环引用 问题,同时提供了一种对 std::shared_ptr 所管理对象的弱引用(non-owning reference)。它不改变所管理对象的引用计数。

  • 循环引用问题
    在使用 std::shared_ptr 时,如果两个对象互相以 shared_ptr 引用彼此,会导致内存泄漏,因为它们的引用计数无法递减到 0。
    • shared_ptr 通过引用计数管理对象的生命周期,当引用计数为 0 时,自动释放对象。
    • 如果存在循环引用,两个对象会始终持有对方,这样它们的引用计数永远不会减为 0,因此无法释放内存。
  • 非拥有性的弱引用
    有时候,一个对象只需要 “观察” 对另一个对象的引用,而无需控制它的生命周期。这时使用 std::weak_ptr 是更合理的选择。

std::weak_ptr 提供了一种临时、不影响生命周期的引用,从而解决了上述问题。

总结

std::weak_ptr 的主要使用场景包括:

  1. 解决 std::shared_ptr 的循环引用问题
  2. 跨组件之间的非拥有性引用,例如缓存对象的管理。
  3. 事件监听器或回调函数,避免悬垂指针的产生
  4. 在弱引用需求场景下提供更加灵活的资源管理,而不是一味增加强引用计数。

std::weak_ptr 的特点

  1. 不控制对象的生命周期
  2. 检测对象是否已销毁
    • 可以通过调用 weak_ptrexpired() 方法来检查被引用的对象是否已经销毁。
  3. 使用 lock() 转换为 shared_ptr
    • 如果需要安全地访问被引用的对象,可以调用 weak_ptrlock() 方法,返回一个临时的 shared_ptr。如果对象已销毁,lock() 会返回一个空指针。

假设我们有两个类 AB,它们通过 std::shared_ptr 互相引用。如果没有使用 weak_ptr,将会发生循环引用,导致内存泄漏。

示例代码(循环引用问题)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <memory>

class B; // 前向声明

class A {
public:
std::shared_ptr<B> ptrB; // A 持有共享指针引用 B
~A() { std::cout << "A destroyed" << std::endl; }
};

class B {
public:
std::shared_ptr<A> ptrA; // B 持有共享指针引用 A
~B() { std::cout << "B destroyed" << std::endl; }
};

int main() {
// 创建循环引用
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->ptrB = b; // A 持有 B
b->ptrA = a; // B 持有 A

// 离开 main() 作用域时,A 和 B 的引用计数不会减到 0,导致内存泄漏
return 0;
}

运行结果:

1
# 没有输出,因为 `A` 和 `B` 无法正常析构,发生内存泄漏。

解决循环引用的正确做法
将其中一个引用改为 std::weak_ptr,避免两个对象互相增加引用计数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class B; // 前向声明

class A {
public:
std::weak_ptr<B> ptrB; // 弱引用 B
~A() { std::cout << "A destroyed" << std::endl; }
};

class B {
public:
std::shared_ptr<A> ptrA; // 共享指针持有 A
~B() { std::cout << "B destroyed" << std::endl; }
};

int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->ptrB = b; // A 弱引用 B
b->ptrA = a; // B 持有 A

// 离开作用域时,A 和 B 都将正确析构
return 0;
}

运行结果:

1
2
B destroyed
A destroyed

通过将某一侧的引用改为 std::weak_ptr,打破了循环引用。


跨组件间的弱引用

如果某些对象之间并无强依赖关系,但仍需临时引用,则可以使用 std::weak_ptr

示例 1:缓存管理
在缓存系统中,如果一个对象的存在依赖于被缓存的内容,则可以使用 std::weak_ptr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <memory>
#include <unordered_map>
#include <string>

class CachedObject {
public:
CachedObject(std::string name) : name(name) {
std::cout << name << " created" << std::endl;
}
~CachedObject() {
std::cout << name << " destroyed" << std::endl;
}
void printName() { std::cout << "Object name: " << name << std::endl; }

private:
std::string name;
};

int main() {
std::unordered_map<std::string, std::weak_ptr<CachedObject>> cache;

{
auto obj1 = std::make_shared<CachedObject>("Object1");
cache["key1"] = obj1;

auto obj2 = std::make_shared<CachedObject>("Object2");
cache["key2"] = obj2;

// 使用缓存中的对象
if (auto obj = cache["key1"].lock()) {
obj->printName(); // 输出:Object name: Object1
}
} // obj1 和 obj2 均超出作用域,被释放

// 尝试访问释放的对象
if (cache["key1"].expired()) {
std::cout << "Object1 no longer exists" << std::endl; // 输出
}

return 0;
}

运行结果:

1
2
3
4
5
6
Object1 created
Object2 created
Object name: Object1
Object2 destroyed
Object1 destroyed
Object1 no longer exists

事件回调(防止悬垂引用)

如果某类对象注册了一个事件监听器或回调函数,而监听器的生命周期可能比被观察的对象短,那么可以使用 std::weak_ptr 避免访问悬垂的指针。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>
#include <memory>
#include <functional>

class EventSource;

class Listener {
public:
Listener(std::shared_ptr<EventSource> source) : source(source) {}
void onEvent() {
if (auto src = source.lock()) { // 检查 source 是否仍有效
std::cout << "Event handled" << std::endl;
} else {
std::cout << "Source no longer exists" << std::endl;
}
}

private:
std::weak_ptr<EventSource> source;
};

class EventSource : public std::enable_shared_from_this<EventSource> {
public:
void fireEvent() {
if (listener) listener(); // 调用回调
}

void setListener(std::function<void()> callback) {
listener = callback;
}

private:
std::function<void()> listener;
};

int main() {
std::shared_ptr<EventSource> source = std::make_shared<EventSource>();
{
auto listener = std::make_shared<Listener>(source);
source->setListener([listener]() { listener->onEvent(); });

source->fireEvent(); // 输出:Event handled
}

// Listener 已销毁,无悬垂引用
source->fireEvent(); // 不输出,Listener 已解除绑定

return 0;
}