看柠檬微趣c++客户端一面面经,发现shared_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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include<cstddef>
struct ControlBlock
{
size_t shared_count;//引用计数
ControlBlock():shared_count(1){}
};
template <typename T>
class Shared_ptr
{
private:
T* ptr;
ControlBlock* ctrl;
void release()//一个私有的释放函数
{
if(ctrl)
{
--ctrl->shared_count;
if(!ctrl->shared_count)
{
delete ctrl;
delete ptr;//如果引用计数为0,则自动销毁该引用计数块和对应的指针
}
ctrl=nullptr;
ptr=nullptr;
}
}

public:
//默认构造函数
Shared_ptr():ptr(nullptr),ctrl(nullptr);
//有参数构造函数
Shared_ptr(T* p):ptr(p),ctrl(p?new ControlBlock:nullptr)
//拷贝构造函数(注意拷贝构造函数是const,且要增加引用计数)
Shared_ptr(const Shared_ptr& other)
{
ctrl=other.ctrl;
ptr=other.ptr;
if(ctrl)
++ctrl->shared_count;
}
//移动构造函数(注意是右值引用,不增加引用计数,要将原指针置空)
Shared_ptr(Shared_ptr&& other) noexcept
{
ctrl=other.ctrl;
ptr=other.ptr;
other.ptr=nullptr;
other.ctrl=nullptr;
}
//拷贝构造运算符
Shared_ptr& operater=(const Shared_ptr& other)
{
if(this!=other)
{
release();
ptr=other.ptr;
ctrl=other.ctrl;
if(ctrl)
{
++ctrl->shared_count;
}
}
return *this;
}
Shared_ptr& operater=(Shared_ptr&& other) noexcept
{
if(this!=other)
{
release();
ptr=other.ptr;
ctrl=other.ctrl;
other.ptr=nullptr;
other.ctrl=nullptr;
}
return *this;
}
~Shared_ptr()
{
release();
}
//重载操作符,解引用和取地址
T* operater->() const {return ptr;}
T& operater&() const {return *ptr;}

// 获取原始指针
T* get() const { return ptr; }

// 获取引用计数
size_t use_count() const { return ctrl ? ctrl->shared_count : 0; }

// 检查是否为空
bool empty() const { return ptr == nullptr; }
};

C++ 智能指针:shared_ptr, weak_ptr, unique_ptr, scoped_ptr

智能指针是 C++ 中用于自动管理动态分配内存的工具,避免手动 newdelete 带来的内存泄漏或悬空指针问题。以下是对 shared_ptrweak_ptrunique_ptrscoped_ptr 的特点、区别和应用场景的对比。

1. shared_ptr

特点

  • 共享所有权:多个 shared_ptr 可指向同一对象,通过引用计数 (use_count) 管理资源。
  • 自动释放:当最后一个 shared_ptr 销毁或重置时,释放资源。
  • 线程安全:引用计数操作是线程安全的,但对象访问需用户保证。
  • 支持自定义删除器:可指定释放资源的方式。
  • 标准库实现:C++11 引入,位于 <memory>

实现机制

  • 使用控制块存储引用计数和删除器。
  • 拷贝增加引用计数,析构减少引用计数,计数为 0 时释放资源。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <memory>
#include <iostream>

struct Test {
~Test() { std::cout << "Test destroyed\n"; }
};

int main() {
auto sp1 = std::make_shared<Test>();
std::cout << "sp1 use_count: " << sp1.use_count() << "\n"; // 1
{
auto sp2 = sp1; // 共享所有权
std::cout << "sp2 use_count: " << sp2.use_count() << "\n"; // 2
} // sp2 销毁,计数减 1
std::cout << "sp1 use_count: " << sp1.use_count() << "\n"; // 1
} // sp1 销毁,资源释放,输出 "Test destroyed"

应用场景

  • 共享资源:多个对象需要共享同一资源(如共享配置对象)。
  • 动态资源管理:需要动态分配内存且多个指针可能引用。
  • 复杂生命周期:对象生命周期不明确,需自动管理。

注意事项

  • 避免循环引用(可能导致内存泄漏)。
  • 使用 std::make_shared 构造以优化性能(单次分配)。

2. weak_ptr

特点

  • 非拥有引用:指向 shared_ptr 管理的对象,但不控制其生命周期。
  • 避免循环引用:不会增加 shared_ptr 的引用计数。
  • 需检查有效性:通过 lock() 获取 shared_ptr 检查对象是否存活。
  • 标准库实现:C++11 引入,位于 <memory>

实现机制

  • shared_ptr 共享控制块,跟踪对象但不影响引用计数。
  • 提供 expired() 检查对象是否已释放,lock() 返回有效 shared_ptr 或空指针。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <memory>
#include <iostream>

int main() {
auto sp = std::make_shared<int>(42);
std::weak_ptr<int> wp = sp; // 弱引用
std::cout << "wp expired: " << wp.expired() << "\n"; // 0 (未过期)
{
auto sp2 = wp.lock(); // 获取 shared_ptr
if (sp2) std::cout << "Value: " << *sp2 << "\n"; // 42
}
sp.reset(); // 释放资源
std::cout << "wp expired: " << wp.expired() << "\n"; // 1 (已过期)
}

应用场景

  • 打破循环引用:如树结构中父子节点互相引用。
  • 缓存:临时访问共享资源但不控制其生命周期。
  • 事件监听:观察对象状态而不延长其生命周期。

注意事项

  • 必须通过 lock() 访问对象,检查是否有效。
  • 不能直接解引用,需转换为 shared_ptr

3. unique_ptr

特点

  • 独占所有权:单一指针拥有资源,禁止拷贝,只能移动。
  • 轻量高效:无引用计数开销,性能优于 shared_ptr
  • 自动释放:离开作用域时自动删除资源。
  • 支持自定义删除器:可指定资源释放方式。
  • 标准库实现:C++11 引入,位于 <memory>

实现机制

  • 使用 RAII 管理资源,析构时调用删除器。
  • 移动构造/赋值转移所有权,原始指针置空。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <memory>
#include <iostream>

struct Test {
~Test() { std::cout << "Test destroyed\n"; }
};

int main() {
auto up = std::make_unique<Test>(); // 独占所有权
// auto up2 = up; // 错误:不可拷贝
auto up2 = std::move(up); // 转移所有权
std::cout << "up empty: " << (up == nullptr) << "\n"; // 1
} // up2 销毁,输出 "Test destroyed"

应用场景

  • 独占资源:对象只需要一个所有者(如文件句柄、单例资源)。
  • 工厂模式:返回独占对象的所有权。
  • 性能敏感场景:需要高效内存管理。

注意事项

  • 使用 std::make_unique (C++14) 构造以避免异常安全问题。
  • 适合需要明确所有权的场景。

4. scoped_ptr (Boost)

特点

  • 非标准库:来自 Boost 库(C++ 标准库无 scope_ptr,可能是指 boost::scoped_ptr)。
  • 严格作用域所有权:不可拷贝、不可移动,仅在定义作用域内有效。
  • 轻量:无引用计数,简单高效。
  • 自动释放:离开作用域时删除资源。
  • 不支持自定义删除器:固定使用 delete

实现机制

  • 类似 unique_ptr 但更严格,禁止任何所有权转移。
  • 析构时直接删除资源。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
#include <boost/scoped_ptr.hpp>
#include <iostream>

struct Test {
~Test() { std::cout << "Test destroyed\n"; }
};

int main() {
boost::scoped_ptr<Test> sp(new Test());
// boost::scoped_ptr<Test> sp2 = sp; // 错误:不可拷贝
// sp2 = std::move(sp); // 错误:不可移动
} // sp 销毁,输出 "Test destroyed"

应用场景

  • 严格局部资源:确保资源在单一作用域内使用(如函数内临时对象)。
  • 简单 RAII:无需转移所有权的场景。
  • 替换原始指针:提高局部变量的安全性。

注意事项

  • 不支持所有权转移,灵活性低于 unique_ptr
  • Boost 库依赖,可能不适用于标准 C++ 项目。

对比总结

特性 shared_ptr weak_ptr unique_ptr scoped_ptr (Boost)
所有权 共享(引用计数) 无所有权(弱引用) 独占(单一所有者) 独占(不可转移)
拷贝 支持 支持 不支持 不支持
移动 支持 支持 支持 不支持
自定义删除器 支持 无(依赖 shared_ptr 支持 不支持
线程安全 引用计数线程安全 引用计数线程安全 无需线程安全(独占) 无需线程安全(独占)
性能开销 较高(引用计数+控制块) 中等(依赖 shared_ptr 低(无引用计数) 最低(无引用计数、无转移)
标准库 C++11 (<memory>) C++11 (<memory>) C++11 (<memory>) Boost 库

数学表示

  • shared_ptr 的引用计数可表示为:
    [
    \text{use_count}(p) = \sum_{i} \text{shared_ptr}_i \text{ pointing to } p
    ]
    其中,资源在 (\text{use_count} = 0) 时释放。
  • weak_ptr 的有效性检查:
    [
    \text{lock}() = \begin{cases}
    \text{shared_ptr}(p), & \text{if } \text{use_count} > 0 \
    \text{nullptr}, & \text{otherwise}
    \end{cases}
    ]
  • unique_ptrscoped_ptr 的所有权单一,生命周期严格绑定作用域。

应用场景总结

  • **shared_ptr**:多所有者共享资源,如对象池、复杂数据结构。
  • **weak_ptr**:打破循环引用、缓存、事件监听。
  • **unique_ptr**:独占资源、工厂模式、传递所有权。
  • **scoped_ptr**:严格局部资源管理,简单 RAII。

注意事项

  • 优先使用 std::make_sharedstd::make_unique 构造智能指针以确保异常安全。
  • 避免将原始指针直接传入智能指针构造函数(如 shared_ptr<T>(new T)),可能导致重复删除。
  • scoped_ptr 非标准库,使用需依赖 Boost,若需类似功能,推荐 unique_ptr