ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

C++智能指针介绍

C++智能指针介绍

C++智能指针

#include<memory>// 智能指针定义在此头文件中

智能指针是C++标准库提供的、用于封装原始指针的类模板。其核心优势在于能够自动管理动态分配的内存,通过确定性的析构来防止内存泄漏,从而简化资源管理并提升代码安全性。

1. 独占指针:unique_ptr

  • 在任何给定的时刻,只能有一个unique_ptr指针拥有并管理其指向的内存。
  • 当unique_ptr超出其作用域时,它所管理的内存将自动被释放。
  • unique_ptr不支持拷贝(copy),但支持移动(move)语义以转移所有权。

1.1 创建 unique_ptr

  • 通过已有裸指针创建
Cat *p1 = new Cat(); std::unique_ptr<Cat> u_p1{p1};
  • 通过new创建
std::unique_ptr<Cat> u_p2 {new Cat()};
  • 通过std::make_unique创建
std::unique_ptr<Cat> u_p3 = make_unique<Cat>();

unique_ptr 可以通过 get() 获取原始指针

std::unique_ptr<Cat> u_p3 = make_unique<Cat>(); std::cout << "address: " << u_p3.get() << std::endl;

unique_ptr 实现了 -> 与 * 运算符

  • 可以通过->运算符调用成员函数。
  • 可以通过*运算符解引用。

1.2 unique_ptr与函数调用

  • unique_ptr 不可 copy,只能 move
  • 在做函数参数或是返回值中一定要注意所有权

值传递

需要用std::move来转移内存拥有权

如果参数直接传入std::make_unique语句,会自动转换为std::move

#include <memory> #include <iostream> using namespace std; void fun1(std::unique_ptr<Cat> p){ count << "address" << p->get() << endl; } int main() { std::unique_ptr<Cat> p1 = make_unique<Cat>(); fun1(std::make_unique<Cat>()); // 自动转换为 move fun1(std::move(p1)); return 0; }

调用函数后不能再使用p1,因为p1的所有权已经转移给形参p。在fun1执行完毕后,p离开作用域,其管理的对象会被自动析构。


引用传递

如果设置参数为const则不能改变指向

eg:reset() //清空智能指针

#include <memory> #include <iostream> using namespace std; void fun2(std::unique_ptr<Cat> &p) { count << "address" << p->get() << endl; p.reset(); // 清空 p,再打印地址会指向 0 } void fun3(const std::unique_ptr<Cat> &p) { count << "address" << p->get() << endl; // p.reset(); 修改 p 的指向会崩溃 } int main() { std::unique_ptr<Cat> p2 = make_unique<Cat>(); std::unique_ptr<Cat> p3 = make_unique<Cat>(); fun2(p2); fun3(p3); return 0; }

unique_ptr作为引用传参时,所有权并未转移,因此不需要使用std::move。

在fun2中,由于参数是非常量引用,可以修改指针的指向,例如调用reset()来释放其管理的对象。

在fun3中,因为参数是const引用,可以访问其管理的对象(例如*p),但不能修改指针本身的指向(例如调用reset()),否则会导致编译错误。


作为返回值

指向一个local object

可以用作链式函数

#include <memory> #include <iostream> using namespace std; std::unique_ptr<Cat> fun5(){ std::unique_ptr<Cat> p = make_unique<Cat>(); cout << "address: " << p->get() << endl; // 指向本地堆地址 return p; } int main() { fun5()->get(); // 调用返回的 unique_ptr 的成员函数 cout << "address: " << fun5()->get() << endl; return 0; }

可以直接通过函数返回的unique_ptr调用其成员函数,这得益于返回值优化(RVO)或移动语义,所有权被安全地转移给调用者。


2. 引用计数智能指针:shared_ptr

shared_ptr维护一个引用计数器,该计数器与所管理的动态对象内存相关联。
当发生拷贝构造或拷贝赋值时,计数器加 1;当shared_ptr被销毁或重置时,计数器减 1。

可通过use_count()成员函数获取当前引用计数值。

  • shared_ptr通常被称为共享指针,它允许多个指针共享同一对象的所有权。
  • 与unique_ptr独占所有权不同,shared_ptr支持共享所有权。

2.1 创建 shared_ptr

#include <memory> #include <iostream> using namespace std; int main() { std::shared_ptr<Cat> p1 = make_shared<Cat>(); //std::shared_ptr<Cat> p1 = make_shared<Cat>{new Cat()}; cout << "p1 use count :" << p1.use_count() << endl; // 输出为 1 std::shared_ptr<Cat> p2 = p1; std::shared_ptr<Cat> p3 = p1; cout << "p1 use count :" << p1.use_count() << endl; // 输出为 3 cout << "p2 use count :" << p2.use_count() << endl; // 输出为 3 cout << "p3 use count :" << p3.use_count() << endl; // 输出为 3 p1 = nullptr; cout << "p1 use count :" << p1.use_count() << endl; // 输出为 0 cout << "p2 use count :" << p2.use_count() << endl; // 输出为 2 cout << "p3 use count :" << p3.use_count() << endl; // 输出为 2 p2.reset(); cout << "p2 use count :" << p2.use_count() << endl; // 输出为 0 cout << "p3 use count :" << p3.use_count() << endl; // 输出为 1 }

对象只会被销毁一次。当没有任何shared_ptr指向该对象,即引用计数变为 0 时,对象才会被自动销毁。


2.2 shared_ptr与函数

shared_ptr值传递

函数内部计数器+1

#include <memory> #include <iostream> using namespace std; void fun1(std::shared_ptr<Cat> p) { cout << "p use count :" << p.use_count() << endl; // 输出为 2 } int main() { std::shared_ptr<Cat> p1 = make_shared<Cat>{new Cat()}; fun1(p1); count << "p1 use count :" << p1.use_count() << endl; //输出为1 return 0; }

shared_ptr引用传递

const表示不可改变指向

#include <memory> #include <iostream> using namespace std; #include <memory> #include <iostream> using namespace std; void fun2(std::shared_ptr<Cat> &p) { cout << "p use count :" << p.use_count() << endl; // 输出为 1 p.reset(new Cat()); // p1 的指向会被改变 } void fun3(const std::shared_ptr<Cat> &p) { cout << "p use count :" << p.use_count() << endl; // 输出为 1 } int main() { std::shared_ptr<Cat> p1 = make_shared<Cat>{new Cat()}; fun2(p1); fun3(p1); cout << "p1 use count :" << p1.use_count() << endl; // 输出为 1 return 0; }

作为返回值传递

#include <memory> #include <iostream> using namespace std; std::shared_ptr<Cat> fun5() { std::shared_ptr<Cat> p = std::make_shared<Cat>(new Cat()); return p; // 返回值会触发移动语义或拷贝省略,引用计数通常不变 } int main() { cout << "p1 use count :" << fun5().use_count() << endl; // 输出为 1 return 0; }

shared_ptr与unique_ptr

不能将shared_ptr转换为unique_ptr

unique_ptr可以转换为shared_ptr(通过std::move)

#include <memory> #include <iostream> using namespace std; std::unique_ptr<Cat> fun() { std::unique_ptr<Cat> p = std::make_unique<Cat>(); return p; } int main() { std::unique_ptr<Cat> p1 = std::make_unique<Cat>(); std::shared_ptr<Cat> p2 = std::move(p1); std::cout << "p2 use count: " << p2.use_count() << std::endl; // 输出为 1 std::shared_ptr p3 = fun(); if (p3) { std::cout << "p3 use count: " << p3.use_count() << std::endl; // 输出为 1 } return 0; }

weak_ptr

weak_ptr不拥有所指对象的所有权,因此不能直接使用->运算符或解引用运算符*来访问对象。

#include <memory> #include <iostream> using namespace std; int main() { std::shared_ptr<Cat> p1 = std::make_shared<Cat>(); std::weak_ptr<Cat> w_p1(p1); // 从 shared_ptr 创建 weak_ptr std::shared_ptr<Cat> p2 = w_p1.lock(); // 尝试提升为 shared_ptr,若对象仍存在则成功 return 0; }

可以通过lock()成员函数尝试获取一个对应的shared_ptr。如果原始对象仍然存在(即其引用计数未降为零),则提升成功;否则返回一个空的shared_ptr。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进