当前位置: 首页 > news >正文

C++: 高效使用智能指针的8个建议

前言:智能指针是C++11的新特性,它基于RAII实现,可以自动管理内存资源,避免内存泄漏的发生,但是智能指针也并不是万能的,如果不正确使用智能指针,也会导致内存泄漏的发生,因此,我们需要了解如何高效使用智能指针避免一些可能的陷阱。本文总结了8个关于智能指针的建议,希望对大家有所帮助。

1. 优先使用std::unique_ptr, 再考虑std::shared_ptr

  • shared_ptr的大小是unique_ptr的两倍,因为shared_ptr需要维护一个引用计数。
  • shared_ptr由于占据更多内存,且需要通过原子操作维护引用计数,因此效率是比较慢的。在不开启编译器优化的时候,是比new操作慢10倍,此时不应该使用make_shared、shared_ptr。开启优化后,也大概慢2-3倍。 [2]
  • unique_ptr、make_unique、带少许偏差的make_shared几乎和new、delete具有一样的性能。
  • unique_ptr自动管理内存资源,而几乎没有额外开销。因此效率和new、delete几乎一样。

2. 尽量使用std::make_uniquestd::make_shared

尽量使用std::make_shared<T>而不是shared_ptr<T>(new T)std::make_shared<T>是更异常安全的做法。std::make_shared<T>是一个函数模板,它在动态内存中分配一个对象并初始化它,返回指向此对象的std::shared_ptr<T>std::make_shared<T>的好处是它只进行一次内存分配,而std::shared_ptr<T>(new T)则进行两次内存分配,一次是为T分配内存,另一次是为std::shared_ptr<T>的控制块分配内存。因此,std::make_shared<T>是更好的选择。

例如:

std::shared_ptr<int> sp(new int(42)); // exception unsafe

当new int(42)抛出异常时,sp将不会被创建,从而对应new分配的内存也不会释放,从而导致内存泄漏。

3. 使std::shared_ptr管理的对象或资源线程安全

如果多个线程同时拷贝同一个 shared_ptr 对象,不会有问题,因为 shared_ptr 的引用计数是线程安全的。但是如果多个线程同时修改同一个 shared_ptr 对象,不是线程安全的。因此,如果多个线程同时访问同一个 shared_ptr 对象,并且有写操作,需要使用互斥量来保护。

4. 注意std::shared_ptr的循环引用问题

  • 什么是循环引用问题 ?

循环引用是指两个或多个对象之间通过shared_ptr相互引用,形成了一个环,导致它们的引用计数都不为0,从而导致内存泄漏。

在观察者模式中使用shared_ptr可能会出现循环引用,在下面的程序中,Observer对象和Subject对象相互引用,导致它们的引用计数都不为0,从而导致内存泄漏。

class IObserver {
public:virtual void update(const string& msg) = 0;
};class Subject {
public:void attach(const std::shared_ptr<IObserver>& observer) {observers_.emplace_back(observer);}void detach(const std::shared_ptr<IObserver>& observer) {observers_.erase(std::remove(observers_.begin(), observers_.end(), observer), observers_.end());}void notify(const string& msg) {for (auto& observer : observers_) {observer->update(msg);}}
private:std::vector<std::shared_ptr<IObserver>> observers_;
};class ConcreteObserver : public IObserver {
public:ConcreteObserver(const std::shared_ptr<Subject>& subject) : subject_(subject) {}void update(const string& msg) override {std::cout << "ConcreteObserver " << msg<< std::endl;}
private:std::shared_ptr<Subject> subject_;
};int main() {std::shared_ptr<Subject> subject = std::make_shared<Subject>();std::shared_ptr<IObserver> observer = std::make_shared<ConcreteObserver>(subject);subject->attach(observer);subject->notify("update");return 0;
}
  • 避免循环引用的方法

将Observer类中的subject_成员变量改为weak_ptr,这样就不会导致内存无法正确释放了。

5. 避免使用裸指针创建智能指针

不要用同一个raw pointer初始化多个shared_ptr

因为多个shared_ptr由同一个raw pointer创建时会导致生成两个独立的引用计数控制块,从以下程序可见sp1、sp2的引用计数都为1。

int* p = new int(0);
std::shared_ptr<int> sp1(p);
std::shared_ptr<int> sp2(p);
std::cout<<sp1.use_count()<<std::endl; // 1
std::cout<<sp2.use_count()<<std::endl; // 1

当sp1、sp2销毁时会产生未定义行为,因为shared_ptr的析构函数会释放它所管理的对象,当sp1析构时,会释放p指向的内存,当sp2析构时,会再次释放p指向的内存。

6. 用enable_shared_from_this在类内部中获得一个指向当前对象的shared_ptr

如果通过this指针创建shared_ptr时,相当于通过一个裸指针创建shared_ptr,多次创建会导致多个shared_ptr对象管理同一个内存。当shared_ptr对象销毁时,会释放this指向的内存,但是this指针可能还会被使用,导致程序崩溃。如以下程序所示:

class A {
public:std::shared_ptr<A> get_shared_ptr() {return std::shared_ptr<A>(this); // error}
};

为了解决这个问题,C++11提供了std::enable_shared_from_this模板类,它可以在类内部获得一个指向当前对象的shared_ptr。

使用方法: 继承enable_shared_from_this类;通过shared_from_this()方法返回。

class A : public std::enable_shared_from_this<A> {
public:std::shared_ptr<A> get_shared_ptr() {return shared_from_this();}
};

6. 避免使用std::shared_ptrget()方法

std::shared_ptrget()方法返回一个裸指针,这个裸指针指向std::shared_ptr管理的对象。如果通过delete释放了这个裸指针指向的内存,当std::shared_ptr销毁时,其管理的对象会被再次释放。

7. 使用unique_ptrrelease()方法后,不要忘记手动释放资源

std::unique_ptr调用release()方法后,会释放对资源的所有权,但是不会释放资源本身。因此当std::unique_ptr调用release()方法后,需要手动调用delete释放资源。

8. 使用std::weak_ptrstd::shared_ptr对象前,检查是否失效。

std::weak_ptr是一种弱引用,它不会增加引用计数,因此不会影响资源的释放。std::weak_ptrlock()方法可以返回一个std::shared_ptr对象,但是在使用std::shared_ptr对象前,需要检查std::shared_ptr对象是否失效。

std::weak_ptr可以作为std::shared_ptr的构造函数参数,但如果std::weak_ptr指向的对象已经被释放,那么std::shared_ptr的构造函数会抛出std::bad_weak_ptr异常。

参考

  1. Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11 and C++14. Scott Meyers. O’Reilly Media. 2014.

  2. memory-and-performance-overhead-of-smart-pointer


你好,我是七昂,致力于分享C/C++、操作系统、软件架构等计算机基础知识。如果你有任何问题或者建议,欢迎随时与我交流。如果这篇内容对您有帮助,请点赞关注,之后将会持续分享更多技术干货。希望我们能一起探索程序员修炼之道。感谢你的阅读!
在这里插入图片描述


http://www.mrgr.cn/news/29744.html

相关文章:

  • WPF 应用程序中使用 Prism 框架时,有多种方式可以注册服务和依赖项
  • 数学建模模型算法-Python实现
  • springboot苍穹外卖实战:五、公共字段自动填充(aop切面实现)+新增菜品功能+oss
  • java数据结构与算法:栈
  • 数据重塑:长宽数据转换【基于tidyr】
  • 项目管理-招标文书都有哪些文件且各自作用
  • vue3中如何拿到vue2中的this
  • 【电源专题】一张图理解电源的类型与转换关系
  • 数据库基础
  • 设计模式-依赖注入
  • RocketMQ实战与集群架构详解
  • Python 入门教程(3)基础知识 | 3.6、标准输入与输出
  • 多模态AI技术详解:跨越数据边界的智能未来
  • Springboot与minio:
  • 机器学习中求解模型参数的方法
  • Pytest使用fixture实现token共享
  • 驱动开发知识点
  • 在 Linux 系统中目录架构说明
  • 记录工作中遇到的问题(持续更新~)
  • Kubernetes Ingress
  • C++面试3
  • 根据 IP 地址进行 VPN 分流(详细,亲测,通用)
  • 深度学习 之 常见损失函数简介:名称、作用及用法
  • vue 2表格滚动加载
  • 【VUE】快速上手
  • 心觉:不能成事的根本原因