C++:智能指针

C++: 智能指针

C++智能指针:从代码到实战,带你玩转内存管理

智能指针是C++11送给程序员的"内存管理神器",让动态内存从"手动挡"秒变"自动挡"。告别 newdelete 的心惊胆战,智能指针自动清理战场,帮你远离内存泄漏和悬空指针的噩梦。C++标准库提供了三把利器:unique_ptrshared_ptrweak_ptr,各有绝活。我将通过我的代码,带你边看边聊,拆解它们的用法,再加上实战经验和避坑指南,帮你在C++世界里如鱼得水。这篇博客适合想快速上手又不失深度的开发者,干货多、废话少,上车!


unique_ptr:独占资源的霸道总裁

我的理解

unique_ptr 就像一个占有欲爆棚的老板,独占一个资源,谁也别想抢!它管理动态分配的对象,离开作用域自动销毁,干净利落,绝不留后患。我的代码展示了 unique_ptr 的几招狠活儿,边看代码边聊。

1. 创建和基本操作

我用 make_unique<int>(10) 搞了个 unique_ptr,指向一个值为 10 的整数。解引用直接拿值,简单暴力。

cpp 复制代码
unique_ptr<int> ptr1 = make_unique<int>(10);
cout << "Value:" << *ptr1 << endl; // 输出:Value: 10

这就像给 ptr1 配了个专属保镖,保护整数 10,用完自动撤。

2. 移动语义:资源交接

unique_ptr 不让拷贝,因为资源只能有一个主人。但可以用 std::move 把所有权移交。我把 ptr1 的资源给了 ptr2ptr1 直接变空壳。

cpp 复制代码
unique_ptr<int> ptr2 = move(ptr1);
// ptr1 不再拥有对象,不能解引用
if (ptr1) {
    cout << "Value in ptr1: " << *ptr1 << endl;
} else {
    cout << "ptr1 no longer owns the object." << endl; // 输出:ptr1 no longer owns the object.
}
cout << "Value in ptr2: " << *ptr2 << endl; // 输出:Value in ptr2: 10

这就像把公司大权交给新老板,ptr1 退休,ptr2 上位。

3. 管理数组

unique_ptr 还能管动态数组,用 unique_ptr<int[]> 轻松搞定。我分配了个 5 个整数的数组,初始化成 0、10、20、30、40,遍历输出确认没毛病。

cpp 复制代码
unique_ptr<int[]> ptr3 = make_unique<int[]>(5);
// 初始化数组
for (int i = 0; i < 5; ++i) {
    ptr3[i] = i * 10;
}
// 输出数组内容
for (int i = 0; i < 5; ++i) {
    cout << "arr[" << i << "] = " << ptr3[i] << endl;
    // 输出:arr[0] = 0, arr[1] = 10, arr[2] = 20, arr[3] = 30, arr[4] = 40
}

这就像给 unique_ptr 一个货架,摆一排箱子,存取自如,用完自动清空。

4. 自定义删除器

有时候释放资源得有点仪式感,比如打印日志。我用 lambda 写了删除器,释放时输出值。

cpp 复制代码
auto deleter = [](int* ptr) {
    cout << "Custom delete: " << *ptr << endl;
    delete ptr;
};
unique_ptr<int, decltype(deleter)> ptr(new int(40), deleter);
// 输出:Custom delete: 40(当 ptr 销毁时)

这就像给资源配了个管家,销毁时还得喊一声:"40,撤了!"

5. 函数返回 unique_ptr

unique_ptr 特别适合在函数间传资源。我写了个函数返回 unique_ptr,所有权自动转移,接收后直接用。

cpp 复制代码
unique_ptr<int> createUniquePtr() {
    return make_unique<int>(30); // 返回一个动态分配的整数
}
void returnUniquePtr() {
    unique_ptr<int> ptr = createUniquePtr();
    cout << "Value from returned ptr: " << *ptr << endl; // 输出:Value from returned ptr: 30
}

这就像工厂生产个产品,直接快递到你家,省心省力。

6. 与裸指针混用

有时候得用裸指针,但我小心翼翼。用 get()unique_ptr 管理的裸指针,访问值安全又方便,但绝不手动删除。

cpp 复制代码
void useRawPointer(unique_ptr<int>& ptr) {
    int* rawPtr = ptr.get(); // 获取裸指针
    cout << "Raw pointer value: " << *rawPtr << endl; // 输出:Raw pointer value: 50
}
void uniquePtrWithRawPointer() {
    unique_ptr<int> ptr = make_unique<int>(50);
    useRawPointer(ptr);
}

这就像借用老板的车,跑一圈就还回去,绝不拆零件。

补充分析

  • 为啥用 make_unique 它一次性分配内存,异常安全。直接用 unique_ptr<int> ptr(new int(10)) 如果构造时抛异常,可能漏内存,make_unique 稳如老狗。
  • 性能unique_ptr 几乎没额外开销,跟裸指针一样快,适合独占场景,比如函数临时对象或类成员。
  • 坑点 :用 get() 拿裸指针时,千万别 delete,否则程序直接崩。也不要把裸指针塞给另一个智能指针,会双重释放,炸得你找不着北。

shared_ptr:资源共享的团队玩家

我的理解

shared_ptr 像一个共享云盘,多个 shared_ptr 可以访问同一资源,通过引用计数决定啥时候清理。我的代码展示了这家伙的团队协作能力。

1. 创建和共享

我用 make_shared<int>(10) 建了个 shared_ptr,复制给 ptr2,两人值都是 10,引用计数 2。

cpp 复制代码
shared_ptr<int> ptr1 = make_shared<int>(10);
cout << "Value int ptr1:" << *ptr1 << endl; // 输出:Value int ptr1: 10
shared_ptr<int> ptr2 = ptr1;
cout << "Value int ptr2:" << *ptr2 << endl; // 输出:Value int ptr2: 10
cout << "ptr1 use count:" << ptr1.use_count() << endl; // 输出:ptr1 use count: 2

这就像一个共享文件夹,大家都能编辑,系统记录有多少人在线。

2. 引用计数动态变化

我加了个 ptr3,引用计数变 3。ptr3 离开作用域,计数减到 2。ptr1.reset() 后,ptr1 退出,计数清零。

cpp 复制代码
{
    shared_ptr<int> ptr3 = ptr1;
    cout << "ptr1 use count: " << ptr1.use_count() << endl; // 输出:ptr1 use count: 3
} // ptr3 超出作用域,引用计数减少
cout << "ptr1 use count: " << ptr1.use_count() << endl; // 输出:ptr1 use count: 2
ptr1.reset(); // 重置 ptr1
cout << "ptr1 use count: " << ptr1.use_count() << endl; // 输出:ptr1 use count: 0

这就像团队成员陆续下线,最后没人用时,资源自动清理。

3. 自定义删除器

unique_ptr 一样,我给 shared_ptr 加了个删除器,释放时输出值。

cpp 复制代码
auto deleter = [](int* ptr) {
    cout << "Custom delete for value: " << *ptr << endl;
    delete ptr;
};
shared_ptr<int> ptr(new int(40), deleter); // 输出:Custom delete for value: 40(销毁时)

这就像团队解散时,派人喊一声:"40,收工!"

4. 与裸指针交互

get() 拿裸指针,访问值,安全又省事。

cpp 复制代码
shared_ptr<int> ptr = make_shared<int>(50);
int* rawPtr = ptr.get();
cout << "Value from raw pointer: " << *rawPtr << endl; // 输出:Value from raw pointer: 50

这就像借用共享资源瞄一眼,用完还回去。

补充分析

  • 为啥用 make_shared 它一次性分配对象和控制块,效率高,内存省。直接用 shared_ptr(new int(10)) 多一次分配,慢还费空间。
  • 线程安全 :引用计数操作线程安全,但对象本身访问得加锁。比如多线程改 *ptr,得用互斥锁。
  • 坑点 :别把同一裸指针给多个 shared_ptr,比如 shared_ptr<int> p1(new int(10)); shared_ptr<int> p2(p1.get());,双重释放,直接炸。

weak_ptr:低调的资源观察者

我的理解

weak_ptrshared_ptr 的小跟班,不控制资源,只观察,能解决循环引用的大麻烦。我的代码让你看它的低调实力。

1. 观察和有效性检查

我建了个 shared_ptr<int>(值 100),用 weak_ptr 观察。通过 lock() 拿到临时 shared_ptr,确认资源还在。

cpp 复制代码
shared_ptr<int> ptr1 = make_shared<int>(100);
weak_ptr<int> weakPtr = ptr1;
shared_ptr<int> ptr2 = weakPtr.lock();
if (ptr2) {
    cout << "Object is still valid, value: " << *ptr2 << endl; // 输出:Object is still valid, value: 100
} else {
    cout << "Object is expired" << endl;
}

这就像探头看看资源还在不在,确认没事再动手。

2. 对象失效

我用 ptr1.reset() 干掉资源,weak_ptrlock() 就拿不到东西,输出对象已过期。

cpp 复制代码
ptr1.reset(); // 销毁 ptr1
shared_ptr<int> ptr3 = weakPtr.lock();
if (ptr3) {
    cout << "Object is still valid, value: " << *ptr3 << endl;
} else {
    cout << "Object is expired" << endl; // 输出:Object is expired
}

这就像发现资源没了,weak_ptr 乖乖认怂。

3. 解决循环引用

循环引用是 shared_ptr 的死穴。我先秀了个错误案例:两个 Node 互相用 shared_ptr 引用,引用计数永不为 0,内存泄漏。

cpp 复制代码
namespace False {
    class Node {
    public:
        shared_ptr<Node> next;
        ~Node() { cout << "Node destroyed" << endl; }
    };
    void circularReferenceExample() {
        shared_ptr<Node> node1 = make_shared<Node>();
        shared_ptr<Node> node2 = make_shared<Node>();
        node1->next = node2; // node1 引用 node2
        node2->next = node1; // node2 引用 node1
        // 循环引用,node1 和 node2 永远不销毁
    }
}

这就像两个死党互相拉着不放,谁也走不了,内存白占着。

然后我用 weak_ptr 解决问题:prevweak_ptr,打破循环,作用域结束时对象正常销毁。

cpp 复制代码
namespace True {
    class Node {
    public:
        shared_ptr<Node> next;
        weak_ptr<Node> prev; // 用 weak_ptr 避免循环引用
        ~Node() { cout << "Node destroyed" << endl; }
    };
    void resolveCircularReference() {
        shared_ptr<Node> node1 = make_shared<Node>();
        shared_ptr<Node> node2 = make_shared<Node>();
        node1->next = node2;
        node2->prev = node1; // weak_ptr 不增计数
        // 作用域结束,node1 和 node2 正常销毁
    }
}

这就像一个死党放手,另一个就能自由离开,内存干净。

补充分析

  • 作用weak_ptr 不控制生命周期,只看 shared_ptr 资源,适合临时访问或循环引用场景。
  • 典型场景 :双向链表、树结构常用 weak_ptr 防循环引用,比如父子节点关系 HALL。
  • 性能lock() 比直接用 shared_ptr 稍慢,得检查状态,但不增计数,省资源。
  • 坑点weak_ptr 不能直接解引用,必须 lock()shared_ptr,否则编译报错。

总结:智能指针的正确打开方式

通过我的代码和分析,智能指针就是C++内存管理的"超级英雄":

  • unique_ptr:独占资源时,首选它,比如函数临时对象或独占成员变量。
  • shared_ptr:多方共享资源时用,但小心循环引用。
  • weak_ptr :观察资源或破循环引用,专治 shared_ptr 的疑难杂症。
  • 优先 make_uniquemake_shared :安全、高效,远离裸 new
  • 裸指针谨慎用get() 拿裸指针别乱删,也别重复管理。

智能指针让C++代码更安全、更优雅,熟练用它们,希望这篇博客让你对 unique_ptrshared_ptrweak_ptr 了如指掌,赶紧去代码里试试吧!


相关推荐
吗喽对你问好11 分钟前
Java位运算符大全
java·开发语言·位运算
chenglin01619 分钟前
.NET中,const和readonly区别
开发语言·.net
DXM052138 分钟前
牟乃夏《ArcGIS Engine地理信息系统开发教程》学习笔记3-地图基本操作与实战案例
开发语言·笔记·学习·arcgis·c#·ae·arcgis engine
Vaclee1 小时前
JavaScript-基础语法
开发语言·javascript·ecmascript
CodeWithMe2 小时前
【C++】线程池
开发语言·c++
专注API从业者2 小时前
《Go 语言高并发爬虫开发:淘宝商品 API 实时采集与 ETL 数据处理管道》
开发语言·后端·爬虫·golang
wuqingshun3141592 小时前
蓝桥杯 2. 确定字符串是否是另一个的排列
数据结构·c++·算法·职场和发展·蓝桥杯
欧先生^_^3 小时前
Scala语法基础
开发语言·后端·scala
hu_yuchen3 小时前
C++:BST、AVL、红黑树
开发语言·c++
炯哈哈3 小时前
【上位机——MFC】视图
开发语言·c++·mfc·上位机