首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >彻底理解 unique_ptr

彻底理解 unique_ptr

作者头像
早起的鸟儿有虫吃
发布2025-11-20 15:49:00
发布2025-11-20 15:49:00
1310
举报
Free Ocean Coast photo and picture
Free Ocean Coast photo and picture

(1) 应用场景的门槛

C++做界面开发:MFC、QT、游戏客户端、OpenCV图像处理...

如果进不了相关公司,根本没人给你实践的机会

(2) 基础设施的壁垒

C/C++基础设施开发:分布式数据库、存储系统、中间件...

如果进不了相关公司,根本没人给你实践的机会

即使有开源项目,个人也难有合适的环境练习

像TiFlash、OceanBase、Ceph这些项目,平时根本用不到

没有实际需求,很难静下心来深入研究

(3) 唯一可行的路径

只剩下在普通笔记本上,建立工程,用std标准库写demo练习

对比其他同学怎么做的,

通过考研,面试 进入行业公司,然后直接瞬间进入数据库,os ai 行业 然后再开发相关项目。

你看出来了吗?

最关键的 并不是 c++本身,明白了,进入相关行业,

我为什么痴迷于写demo、研究原理?

都是被现实逼的。

哪怕进入公司 讨论 方案设计,bug 定位解决,没有 人关系 c++新特性,

所以,我能做的就是把能做的事情做好:

一个笔记本,建立项目工程,写一个 cpp 文件,std 标准库,写 demo,git、cmake

把C++11新特性研究透彻

今天 就解决掉最 不关键事情,研究无用事情。

Part 1 面试官提问:

智能指针

unique_ptr 独占所有权 什么场景使用?提示 资源,资源赋值拷贝中传递

内存模型

编译优化

Part 2 我的回答

本文举例代码均来 3FS ,TiFlash 等事项

2.1 智能指针

unique_ptr 独占所有权:资源只有 1 个,传递拷贝只有 1 个

使用场景,情况 3fs 内存池设计与 unique_ptr代替普通指针,减少锁竞争

目的:资源只有 1 个,传递拷贝只有 1 个

代码语言:javascript
复制
┌─────────────────────────────────────────────────────────────┐
│              ObjectPool<T, NumTLSCachedItem, ...>           │
│                    (Template Class)                          │
├─────────────────────────────────────────────────────────────┤
│ ┌─ 类型别名 ─────────────────────────────────────────────┐  │
│ │ using Storage = std::aligned_storage_t<...>           │  │
│ │ using Batch = std::vector<std::unique_ptr<Storage>>    │  │
│ │ using Ptr = std::unique_ptr<T, Deleter>               │  │
│ └─────────────────────────────────────────────────────────┘  │
├─────────────────────────────────────────────────────────────┤
│ ┌─ 私有成员 ─────────────────────────────────────────────┐  │
│ │ - std::mutex mutex_                                    │  │
│ │ - std::vector<Batch> global_                          │  │
│ └─────────────────────────────────────────────────────────┘  │
├─────────────────────────────────────────────────────────────┤
                           │
                           │ 包含
                           │ (1:N, thread_local)
                           ▼
┌─────────────────────────────────────────────────────────────┐
│                         TLS                                  │
│              (Thread Local Storage Class)                    │
├─────────────────────────────────────────────────────────────┤
│ ┌─ 成员变量 ─────────────────────────────────────────────┐  │
│ │ - ObjectPool& parent_                                  │  │
│ │ - Batch first_                                         │  │
│ │ - Batch second_                                        │  │
│ └─────────────────────────────────────────────────────────┘  │
├─────────────────────────────────────────────────────────────┤
│ ┌─ 方法 ─────────────────────────────────────────────────┐  │
│ │ + std::unique_ptr<Storage> get()                       │  │
│ │ + void put(std::unique_ptr<Storage> obj)               │  │
│ └─────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
         │                              │
         │ 包含                          │ 包含
         │ (1:1)                         │ (1:1)
         ▼                              ▼
┌──────────────────────┐      ┌──────────────────────┐
│   Batch (first_)     │      │   Batch (second_)    │
│  ┌────────────────┐  │      │  ┌────────────────┐  │
│  │ vector<unique_ │  │      │  │ vector<unique_ │  │
│  │   ptr<Storage>>│  │      │  │   ptr<Storage>>│  │
│  │                │  │      │  │                │  │
│  │ [最多64个元素]  │  │      │  │ [溢出缓存]     │  │
│  └────────────────┘  │      │  └────────────────┘  │
└──────────────────────┘      └──────────────────────┘



┌─────────────────────────────────────────────────────────────┐
│              ObjectPool 全局结构                              │
├─────────────────────────────────────────────────────────────┤
│                                                               │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  static ObjectPool instance  (单例,全局唯一)       │    │
│  │  └─ mutex_                                          │    │
│  │  └─ global_: vector<Batch>                          │    │
│  │      ├─ Batch[0]                                    │    │
│  │      ├─ Batch[1]                                    │    │
│  │      └─ ...                                         │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                               │
│  每个线程:                                                    │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  thread_local TLS tls{instance}  (每线程一个)      │    │
│  │  └─ parent_ → instance                             │    │
│  │  └─ first_: Batch                                  │    │
│  │      └─ [64个 unique_ptr<Storage>]                 │    │
│  │  └─ second_: Batch                                 │    │
│  │      └─ [溢出缓存]                                  │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                               │
└─────────────────────────────────────────────────────────────┘

using Storage = std::aligned_storage_t<sizeof(T), alignof(T)>; 代表:一个连续的内存地址,8 字节,16 字节,32 字节

代码语言:javascript
复制
 using Batch = std::vector<std::unique_ptr<Storage>> //存储中不发生拷贝

代表:存储多个连续内存地址(没有调用构造函数呢)

1

只存储指针,vector 默认存储值,内存只有1 份,【减少存储中发生拷贝】

2

移动unique_ptr非常轻量(只移动指针)【why unique_ptr不允许拷贝吗?】

3

明确的资源所有权语义

4

自动内存管理,vector销毁时自动清理

其中一个使用方式: using Ptr = std::unique_ptr<T, Deleter>; // 如何释放资源

代码语言:javascript
复制
// 匿名函数 lamber 表达式
struct Deleter
{
	constexpr Deleter() = default;
    void operator()(T *item) {

     item->~T(); //析构函数 ~foo()

      tls().put(std::unique_ptr<Storage>(reinterpret_cast<Storage *>(item)));
     }
};

类型转换:

static_cast 是一种更安全的转换方式,它在编译时检查转换的合法性, 比如整数类型和浮点类型之间,或者将 void* 转换为具体类型的指针。

reinterpret_cast 用于任意指针(或引用)类型之间的转换,但不检查安全性,必须使用 类型 T 不确定

代码语言:javascript
复制

using Ptr = std::unique_ptr<T, Deleter>;
static Ptr get()  //返回不继续拷贝
{
	return Ptr(new (tls().get().release()) T);
}  //返回unique_ptr 这个不是复制 =,移动构造函数
疑问:unique_ptr不允许复制拷贝,为什么作为函数返回值

基础回顾:

现代编译器会应用返回值优化 (RVO)

移动语义允许转移所有权, 移动构造函数 不是赋值操作 a=b vs a(b) vs a(move(b))

代码语言:javascript
复制

// ❌ 错误:尝试拷贝
std::unique_ptr<int> bad_example(std::unique_ptr<int> p) {
    return p;  // 错误:尝试从左值拷贝
}  //a =b

std::unique_ptr<int> good_example1() {
    return std::unique_ptr<int>(new int(42));  // 直接构造临时对象
} //a(b)


std::unique_ptr<int> good_example2() {
    auto p = std::unique_ptr<int>(new int(100));
    return p;  // 编译器将其视为 std::move(p)
} //现代编译器会应用**返回值优化 (RVO) /a(std:move(b))
std::unique_ptr<int> good_example3(std::unique_ptr<int> p) {
    return std::move(p);  // 显式移动
}
疑问:Ptr传递应该是指针呀?为什么 T 类型
代码语言:javascript
复制

// 1. 配合 placement new 使用
void* memory = malloc(sizeof(MyClass));
MyClass* obj = new(memory) MyClass();
obj->~MyClass();  // ✅ 正确
free(memory);

// 2. 在自定义内存池中
obj->~T();  // ✅ 析构对象但不释放池内存

疑问:TLS 如何使用,为什么设计和唯一指针关系

TLS 类图

private:

代码语言:javascript
复制
ObjectPool &parent_; // ObjectPool引用 就是全家变量 vector<Batch> 访问加锁Batch first_;  //主缓存批次Batch second_; //溢出缓存批次

公共方法详细说明:

get(): 获取内存块的完整算法流程

put(): 归还内存块的完整算法流程

代码语言:javascript
复制
// put a object into thread local cache or global cache.

// unique_ptr 参数,智能移动构造
void put(std::unique_ptr<Storage> obj) {

	// push to the first batch. 不满
	if (first_.size() < kThreadLocalMaxNum)
	{
		first_.push_back(std::move(obj));	//obj 变量与地址,不是右值
		return;
	}

	// push to the second batch.

	second_.push_back(std::move(obj));

     //full 
	if (UNLIKELY(second_.size() >= kThreadLocalMaxNum))
	{

		// push to the global cache.

		parent_.putBatch(std::move(second_)); //second 移动被自动走了。

		second_.clear(); //为什么敢 clear 不担心 指针指针对象 析构时候释放空间吗?

		second_.reserve(kThreadLocalMaxNum);//不是 resize,kThreadLocalMaxNum最大容量 ,1 2 4 8 多申请方式。

	}

}
疑问:vector push_back 支持左值传递,支持右值传递 重载 2 个函数吗?为不写成一个 ?

对,重载 2 个

void push_back( const T& value )

void push_back( T&& value )

https://en.cppreference.com/w/cpp/container/vector/push_back

疑问:vector push_back是函数本身实现 T 类似移动拷贝吗?

不是

代码语言:javascript
复制
void push_back(value_type&& __x)
{
    if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage)
    {
        _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
                                 std::move(__x));
        ++this->_M_impl._M_finish;
    }
    else
        _M_realloc_insert(end(), std::move(__x));//
}

https://en.cppreference.com/w/cpp/memory/unique_ptr/unique_ptr.html

unique_ptr( unique_ptr&& u ) noexcept

explicit unique_ptr( pointer p ) noexcept;


template<typename T>
class unique_ptr {
    T* ptr_; // 底层原始指针

public:
    // 移动构造函数
    unique_ptr(unique_ptr&& other) noexcept
        : ptr_(other.ptr_) {  // 1. 接管资源所有权
        other.ptr_ = nullptr; // 2. 置空原指针
    }

    // 禁用拷贝构造
    unique_ptr(const unique_ptr&) = delete;

    // ... 其他成员函数
};

疑问:vector push_back参数value_type__x 为什么,还用std::move(__x)?这不是std::move(std::move(std::move(__x)))

std::move() 将左值,或者右值 转化右值

右值 :临时值,无法获取变量地址

当函数内部,x 变量有地址,不在右值,需要再次转化

需要再次std::move(std::move(std::move(__x)))

其他场景 独占所有权:每个资源只属于一个所有者
网络连接管理
代码语言:javascript
复制
Result<std::unique_ptr<TcpSocket>> acquire(Address addr);
std::unique_ptr<Socket> socket_;

管理网络连接的所有权转移

连接池中连接的获取和归

别想太多--只管去面

别想太多--只管去面

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-11-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 后端开发成长指南 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Part 1 面试官提问:
    • 内存模型
    • 编译优化
  • Part 2 我的回答
    • 2.1 智能指针
      • unique_ptr 独占所有权:资源只有 1 个,传递拷贝只有 1 个
      • 疑问:vector push_back参数value_type__x 为什么,还用std::move(__x)?这不是std::move(std::move(std::move(__x)))
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档