C++ 高效使用智能指针的8个建议
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_unique
和std::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_ptr
的get()
方法
std::shared_ptr
的get()
方法返回一个裸指针,这个裸指针指向std::shared_ptr
管理的对象。如果通过delete释放了这个裸指针指向的内存,当std::shared_ptr
销毁时,其管理的对象会被再次释放。
7. 使用unique_ptr
的release()
方法后,不要忘记手动释放资源
std::unique_ptr
调用release()
方法后,会释放对资源的所有权,但是不会释放资源本身。因此当std::unique_ptr
调用release()
方法后,需要手动调用delete释放资源。
8. 使用std::weak_ptr
的std::shared_ptr
对象前,检查是否失效。
std::weak_ptr
是一种弱引用,它不会增加引用计数,因此不会影响资源的释放。std::weak_ptr
的lock()
方法可以返回一个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
异常。
参考
Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11 and C++14. Scott Meyers. O'Reilly Media. 2014.
你好,我是七昂,计算机科学爱好者,致力于分享C/C++、操作系统、软件架构等计算机基础知识。如果你有任何问题或者建议,欢迎随时与我交流。如果这篇内容对您有帮助,请点赞关注,之后将会持续分享更多技术干货。希望我们能一起探索程序员修炼之道。感谢你的阅读!
C++ 高效使用智能指针的8个建议的更多相关文章
- 智能指针(1)-std::unique_ptr
std::unique_ptr std::unique_ptr是一种几乎和原始指针一样高效的智能指针,对所管理的指针资源拥有独占权.由C++11标准引入,用于替代C++98中过时的std::auto_ ...
- c/c++ 智能指针 shared_ptr 和 new结合使用
智能指针 shared_ptr 和 new结合使用 用make_shared函数初始化shared_ptr是最推荐的,但有的时候还是需要用new关键字来初始化shared_ptr. 一,先来个表格,唠 ...
- C++11智能指针读书笔记;
智能指针是一个类对象,而非一个指针对象. 原始指针:通过new建立的*指针 智能指针:通过智能指针关键字(unique_ptr, shared_ptr ,weak_ptr)建立的指针 它的一种通用实现 ...
- 「C++」理解智能指针
维基百科上面对于「智能指针」是这样描述的: 智能指针(英语:Smart pointer)是一种抽象的数据类型.在程序设计中,它通常是经由类型模板(class template)来实做,借由模板(tem ...
- shared_ptr智能指针源码剖析
(shared_ptr)的引用计数本身是安全且无锁的,但对象的读写则不是,因为 shared_ptr 有两个数据成员,读写操作不能原子化.根据文档 (http://www.boost.org/doc/ ...
- 读书笔记 effective c++ Item 17 使用单独语句将new出来的对象放入智能指针
1. 可能会出现资源泄漏的一种用法 假设我们有一个获取进程优先权的函数,还有一个在动态分类的Widget对象上根据进程优先权进行一些操作的函数: int priority(); void proces ...
- 智能指针剖析(上)std::auto_ptr与boost::scoped_ptr
1. 引入 C++语言中的动态内存分配没有自动回收机制,动态开辟的空间需要用户自己来维护,在出函数作用域或者程序正常退出前必须释放掉. 即程序员每次 new 出来的内存都要手动 delete,否则会造 ...
- C++智能指针剖析(上)std::auto_ptr与boost::scoped_ptr
1. 引入 C++语言中的动态内存分配没有自动回收机制,动态开辟的空间需要用户自己来维护,在出函数作用域或者程序正常退出前必须释放掉. 即程序员每次 new 出来的内存都要手动 delete,否则会造 ...
- C++解析(27):数组、智能指针与单例类模板
0.目录 1.数组类模板 1.1 类模板高效率求和 1.2 数组类模板 1.3 堆数组类模板 2.智能指针类模板 2.1 使用智能指针 2.2 智能指针类模板 3.单例类模板 3.1 实现单例模式 3 ...
- C++解析(20):智能指针与类型转换函数
0.目录 1.智能指针 2.转换构造函数 3.类型转换函数 4.小结 1.智能指针 内存泄漏(臭名昭著的Bug): 动态申请堆空间,用完后不归还 C++语言中没有垃圾回收机制 指针无法控制所指堆空间的 ...
随机推荐
- MySql 数据库、数据表操作
数据库操作 创建数据库 语法 语法一:create database 数据库名 语法二:create database 数据库名 character set 字符集; 查看数据库 语法 查看数据库服务 ...
- Golang 切片作为函数参数传递的陷阱与解答
作者:林冠宏 / 指尖下的幽灵.转载者,请: 务必标明出处. GitHub : https://github.com/af913337456/ 出版的书籍: <1.0-区块链DApp开发实战&g ...
- 基于Docker安装项目管理工具禅道
禅道是通用的项目管理软件 完整支持敏捷项目模型.瀑布项目模型.看板模型 内置项目集.产品.项目和执行四个管理框架 支持CMMI标准的落地实施 下载镜像 docker pull easysoft/zen ...
- Swift开发基础06-闭包
Swift的闭包(Closures)是一种将功能块和上下文整合并演示在代码中的一种手段.闭包可以捕获并存储其上下文中的变量和常量.与普遍存在于其他语言的匿名函数(如Python的lambda.Java ...
- VS Code 开发统一代码格式化配置
eslint: 是用来做代码风格检查的,比较关注代码质量,并且会提示不符合风格规范的代码,也有一部分代码格式化的功能.不是消除空行. "editor.formatOnSave": ...
- 第一章 FFmpeg初体验:在Centos7.9下编译FFmpeg!
FFmpeg 官方网站:https://ffmpeg.org//download.html#build-linux 1.下载源码 1.1 第一种方式,官网上面下载源码包: 截至目前最新的版本是7.0. ...
- AT_arc113_c 题解
洛谷链接&Atcoder 链接 本篇题解为此题较简单做法及较少码量,并且码风优良,请放心阅读. 题目简述 现在有一个字符串 \(S\),每一次你可以选择一个 \(i(1 \le i \le | ...
- 学习笔记--Java方法重载
Java方法重载 感受 以下代码不使用"方法重载",不使用overload,分析程序存在的缺点 public class OverloadTest01 { // 入口 public ...
- Python 基于pymongo操作Mongodb学习总结
实践环境 Python 3.6.4 pymongo 4.1.1 pymongo-3.12.3-cp36-cp36m-win_amd64.whl 下载地址:https://pypi.org/simple ...
- 配置Sprig security后Post请求无法使用
在学习过程中发现在配置完Spring security后,Post请求失效,无法增删改数据,这里可以通过在Spring Security 的Config类中增加 也可以自定义csrf,不过目前还不是很 ...