一. make系列函数

(一)三个make函数

  1. std::make_shared:用于创建shared_ptr。GCC编译器中,其内部是通过调用std::allocate_shared来实现的

  2. std::make_unique:C++14中加入标准库。

  3. std::allocate_shared:行为和std::make_shared一样,只不过第1个实参是个用以动态分配内存的分配器对象。

//make_unique的模拟实现
template<typename T, typename...Ts>
std::unique_ptr<T> make_unique(Ts&&...params)
{
return std::unique_ptr<T>(new T(std::forward<Ts>(params)...));
} //make_shared的实现(GCC编译器)
template<typename _Tp, typename... _Args>
inline shared_ptr<_Tp> make_shared(_Args&&... __args)
{
typedef typename std::remove_const<_Tp>::type _Tp_nc;
return std::allocate_shared<_Tp>(std::allocator<_Tp_nc>(),
std::forward<_Args>(__args)...);
}

std::make_unique和std::make_shared的实现

(二)与new相比,make系列函数的优势

  1. 避免代码冗余:创建智能指针时,被创建对象的类型只需写1次。如make_shared<T>(),而用new创建智能指针时,需要写2次。

  2. 异常安全:make系列函数可编写异常安全代码,改进了new的异常安全性。

  3. 提升性能:编译器有机会利用更简洁的数据结构产生更小更快的代码。使用make_shared<T>时会一次性进行内存分配,该内存单块(single chunck)既保存了T对象又保存与其相关联的控制块。而直接使用new表达式,除了为T分配一次内存,还要为与其关联的控制块再进行一次内存分配。

二. make系列函数的局限

(一)所有的make系列函数都不允许自定义删除器

(二)make系列函数创建对象时,不能接受{}初始化列表。(这是因为完美转发的转发函数是个模板函数,它利用模板类型进行推导。因此无法将“{}”推导为initializer_list,具体见《完美转发》一课)。换言之,make系列只能将圆括号内的形参完美转发。

(三)自定义内存管理的类(如重载了operator new 和operator delete),不建议使用make_shared来创建。原因如下:

  1. 重载operator new和operator delete时,往往用来分配和释放该类精确尺寸(sizeof(T))的内存块。

  2. 而make_shared创建的shared_ptr,是一个自定义了分配器(std::allocate_shared)和删除器的智能指针,由allocate_shared分配的内存大小也不等于上述的尺寸,而是在此基础上加上控制块的大小。

  3. 因此,不建议使用make函数为那些重载了operator new和operator delete的类创建对象。

(四)对象的内存可能无法及时回收

  1. make_shared 只分配一次内存,减少了内存分配的开销。使得控制块和托管对象在同一内存块上分配。而控制块是由shared_ptr和weak_ptr共享的,因此两者共同管理着这个内存块(托管对象+控制块)。

  2. 当强引用计数为0时托管对象被析构(即析构函数被调用),但内存块并未被回收,只有等到最后一个weak_ptr离开作用域时,弱引用也减为0才会释放这块内存块。原本强引用减为0时就可以释放的内存, 现在变为了强引用和弱引用都减为0时才能释放, 意外的延迟了内存释放的时间。这对于内存要求高的场景来说, 是一个需要注意的问题。

  3.因此,当内存紧张且托管对象非常大时,如果weak_ptr的生命期比shared_ptr更长时不建议使用make_shared

【编程实验】make系列函数的优劣

#include <iostream>
#include <memory> //for smart pointer
#include <vector> using namespace std; class Widget
{
public:
Widget(){}
Widget(int x, int y){ cout << "Widget(int x, int y)" << endl; }
Widget(const std::initializer_list<int> li) { cout << "Widget(std::initializer_list<int> li)"<< endl; }
}; void processWidget(std::shared_ptr<Widget> spw, int priority){}
int computePriority() { /*throw 1;*/ return ; }//假设该函数会抛出异常 class ReallyBigType {};//大对象 int main()
{
//1. make系列函数的优势
//1.1 避免代码冗余,减少重复书写类型
auto upw1(std::make_unique<Widget>()); //使用make系列函数,Widget只需写一次
std::unique_ptr<Widget> upw2(new Widget); //使用new,Widget需写二次。 //1.2 make系统异常安全性更高
//在将实参传递processWidget前,各个参数时必须先被计算出来,假设顺序如下(因编译器和调用约定而异)
//A. 先new Widget,即一个Widget对象在堆上创建。
//B. 执行computePriority,但假设此时该函数产生异常,那上面的堆对象就会泄漏。
//C. 正常流程下,应执行shared_ptr构造函数,但由于第2步的异常,使得第1步分配的堆对象永远不会被这个
// shared_ptr接管(实际上该shared_ptr自己都没有机会创建),于是资源泄漏!
processWidget(shared_ptr<Widget>(new Widget), computePriority());//潜在资源泄漏! //异常安全!
processWidget(make_shared<Widget>(), computePriority()); //如果make_shared首先被调用当computePriority
//发生异常时,则之前的shared_ptr会被释放,
//从而释放Widget对象。如果computePriority先
//调用,则make_shared没有机会被调用,也就不会
//有资源泄漏!
//1.3 make_shared一次性分配内存
auto spw1 = std::make_shared<Widget>(); //一次性分配一个内存单块,可容纳Widget对象和控制块内存
std::shared_ptr<Widget> spw2(new Widget); //2次分配:new和分配控制块各一次。 //2. make系列函数的局限性
//2.1 make不能自定义删除器
auto widgetDeleter = [](Widget* pw) {delete pw; };
std::unique_ptr<Widget, decltype(widgetDeleter)> upw3(new Widget, widgetDeleter);
std::shared_ptr<Widget> spw3(new Widget, widgetDeleter); //2.2 make系列函数不能接受{}初始化
auto upv = std::make_unique<std::vector<int>>(, ); //10个元素,每个都是20。而不是只有两个元素
auto spv = std::make_shared<std::vector<int>>(, ); //同上 auto pw1 = new Widget(, ); //使用圆括号,匹配Widget(int x, int y)
auto pw2 = new Widget{ , }; //使用大括号,匹配Widget(initializer_list)
delete pw1;
delete pw2; auto spw = std::make_shared<Widget>(, ); //使用圆括号,匹配Widget中非initializer_list形参的构造函数
//auto spw = std::make_shared<Widget>({10,20}); //error,make无法转发大括号初始化列表(原因见《完美转发》一课)
auto initList = { , }; //initList推导为initializer_list<int>
auto splst = std::make_shared<Widget>(initList); //ok,匹配Widget(const std::initializer_list<int> li) //2.3 对象的内存可能无法及时回收
auto pBigObj = std::make_shared<ReallyBigType>(); //通过make_shared创建大对象
//... //创建指向大对象的多个std::shared_ptr和std::weak_ptr,并使用这些智能指针来操作对象
//... //最后一个指向大对象的shard_ptr在此析构,但若干weak_ptr仍然存在
//... //此时,内存块只析构,还没回收。因为weak_ptr还共享着内存块中的控制块
//... //最后一个指向大对象的weak_ptr析构,内存块(托管对象+控制块)才被回收。由于weak_ptr的生命期比shared_ptr长,
//出现了内存块延迟回收的现象。 //使用new方法则不会出现上述现象
shared_ptr<ReallyBigType> pBigObj2(new ReallyBigType); //通过new,而不是make_shared创建
//... //同前,创建指向多个指向大对象的shared_ptr和weak_ptr。
//... //最后一个指向大对象的shard_ptr在此析构,但若干weak_ptr仍然存在。此时大对象的内存由于强引用为0,被回收。
//... //此阶段,仅控制块占用的内存处于未回收状态。
//... //最后一个指向该对象的weak_ptr析构,控制块被回收。 return ;
}

第23课 优先选用make系列函数的更多相关文章

  1. epoll 系列函数简介、与select、poll 的区别

    一.epoll 系列函数简介 #include <sys/epoll.h> int epoll_create(int size); int epoll_create1(int flags) ...

  2. php curl_multi系列函数实现多线程抓取网页

    最近几天在做一个多搜索引擎关键字排名查询工具,用于及时方便的了解关键词在各大搜索引擎的排名. 在抓取360搜索的时候,发现360搜索每页只支持显示10个搜索结果,如果想获取100个搜索结果数据,就得搜 ...

  3. exec系列函数和system函数

    一.exec替换进程映象 在进程的创建上Unix采用了一个独特的方法,它将进程创建与加载一个新进程映象分离.这样的好处是有更多的余地对两种操作进行管理.当我们创建 了一个进程之后,通常将子进程替换成新 ...

  4. 线程模型、pthread 系列函数 和 简单多线程服务器端程序

    一.线程有3种模型,分别是N:1用户线程模型,1:1核心线程模型和N:M混合线程模型,posix thread属于1:1模型. (一).N:1用户线程模型 “线程实现”建立在“进程控制”机制之上,由用 ...

  5. POSIX 共享内存和 系列函数

    在前面介绍了system v 共享内存的相关知识,现在来稍微看看posix 共享内存 和系列函数. 共享内存简单来说就是一块真正的物理内存区域,可以使用一些函数将这块区域映射到进程的地址空间进行读写, ...

  6. POSIX 消息队列 和 系列函数

    一.在前面介绍了system v 消息队列的相关知识,现在来稍微看看posix 消息队列. posix消息队列的一个可能实现如下图: 其实消息队列就是一个可以让进程间交换数据的场所,而两个标准的消息队 ...

  7. 【洛谷日报#26】GCC自带位运算系列函数

    文章转自 洛谷 谈到GCC的黑科技,大家想到的一定是这句: #pragma GCC optimize (3)//吸氧 抑或是这句: #pragma GCC diagnostic error " ...

  8. PHP进程通信基础——shmop 、sem系列函数使用

    PHP进程通信基础--shmop .sem系列函数使用 进程通信的原理就是在系统中开辟出一个共享区域,不管是管道也好,还是共享内存,都是这个原理.如果心中有了这个概念,就会很方便去理解代码.由于官网上 ...

  9. PHP 使用 curl_* 系列函数和 curl_multi_* 系列函数进行多接口调用时的性能对比

    在页面中调用的服务较多时,使用并行方式,即使用 curl_multi_* 系列函数耗时要小于 curl_* 系列函数. 测试环境 操作系统:Windows x64 Server:Apache PHP: ...

随机推荐

  1. scrapy学习笔记(一)

    环境:Windows 7  x64   Python3.7.1  pycharm 一.安装scrapy 1.1linux系统使用:pip install scrapy 1.2Windows系统: pi ...

  2. SSO单点登录和CAS

    一.单点登录流程 =====客户端====== 1.拦截客户端的请求判断是否有局部的session 2.1如果有局部的session,放行请求. 2.2如果没有局部session 2.2.1请求中有携 ...

  3. vue中使用axios进行ajax请求数据(跨域配置)

    npm安装axios npm install axios --save 引入axios import axios from 'axios' 使用axios mounted () { this.getH ...

  4. 达能依靠Matrikon进行数据存储和分析

    达能是一家致力于通过食品实现健康的公司,业务遍及五大洲130多个国家.在罗马尼亚,达能每天在布加勒斯特的工厂生产100万瓶酸奶.由于质量和产品安全在达能最为重要,因此监控和分析工业过程需要非常高效才能 ...

  5. 微信小程序使用websocket通讯的demo,含前后端代码,亲测可用

    目录 0.概述websocket 1.app.js写法 2.后台写法 0.概述websocket (1) 个人总结:后台设置了websocket地址,服务器开启后等待有人去连接它. 一个客户端一打开就 ...

  6. sql server 安装出现需要sqlncli.msi文件,错误为 microsoft sql server 2012 native client

    在安装sql server 2017 时出现 弹框标题为  microsoft sql server 2012 native client  内容为需要sqlncli.msi文件 去本地目录找本身的那 ...

  7. SVN Log命令常用组合【转】

    转自:https://blog.csdn.net/xuanwenchao/article/details/8875103 版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请 ...

  8. Django的版本坑

    Django2.x与1.x版本之间的坑 App模块中models.py django2.x是中reverse使用方法 from django.urls import reverse ForeignKe ...

  9. Eyoo大学生交友平台

    团队简介 团队名称 golden express 队员学号列表 王伟 201731062214 刘冬 201731062227 张旭 201731062129 秦裕航 201731062432 (组长 ...

  10. html5 localStorage讲解

    早期的web中使用cookies在客户端保存诸如用户名等简单的信息,但是,在使用cookies存储永久数据存在以下问题. 1.cookies的大小限制在4kB,不适合大量的数据存储. 2.浏览器还限制 ...