网站信息维护方案/深圳网站建设推广优化公司
一、容器修改器的新特性
c++11以前,标准库的容器修改器功能提供了数据插入成员函数inset、push_back,而在 c++11标准化,标准库的容器修改器增加了emplace、emplace_back、emplace_front等插入成员函数。同样是插入函数,两者有何区别呢。
1.1 内存分配器
在c++11中,其实容器的构造及元素空间分配充分利用了std::allocator系列分配器。emplace为原位构造元素,通过 std::allocator_traits::construct 构造元素,用布置 new 在容器提供的位置原位构造元素;将参数 args... 作为 std::forward<Args>(args)... 转发给构造函数。 args... 可以直接或间接地指代容器中的值。
来看一下这些容器的一个类模板定义,都是依赖std::allocator分配器构造的:
//deque
template< class T, class Allocator = std::allocator<T> > class deque;
//list
template< class T, class Allocator = std::allocator<T> > class list;
//map
template< class Key, class T, class Compare = std::less<Key>,class Allocator = std::allocator<std::pair<const Key, T> > > class map;
//set
template< class Key, class Compare = std::less<Key>,class Allocator = std::allocator<Key> > class set;
//.......
c++11标准库提供了各类式分配器。
//分配器,配器是封装内存分配策略的类模板。这允许泛型容器从数据自身将内存管理解耦合。
//定义于头文件 <memory>
allocator //默认的分配器(类模板)
allocator_traits //(C++11) 提供关于分配器类型的信息(类模板)
allocation_result //(C++23) 记录由 allocate_at_least 分配的存储的地址与实际大小(类模板)
allocate_at_least //(C++23) 经由分配器分配至少与请求的大小一样大的存储(函数模板)
allocator_arg_t //(C++11) 标签类型,用于选择具分配器的构造函数重载(类)
allocator_arg //(C++11) 用于选择具分配器的构造函数的 std::allocator_arg_t 对象(常量)
uses_allocator //(C++11) 检查指定的类型是否支持使用分配器的构造(类模板)
uses_allocator_construction_args //(C++20) 准备匹配给定类型所要求的使用分配器构造的口味的参数列表(函数模板)
make_obj_using_allocator //(C++20) 以使用分配器构造的手段创建给类型的对象(函数模板)
uninitialized_construct_using_allocator //(C++20) 以使用分配器构造的手段在指定的内存位置创建给定类型的对象(函数模板) //定义于头文件 <scoped_allocator>
scoped_allocator_adaptor //(C++11) 为多级容器实现的多级分配器(类模板) //定义于头文件 <memory_resource>,定义于命名空间 std::pmr
polymorphic_allocator (C++17) //以 std::memory_resource 构造,支持基于它的运行时多态的分配器(类模板)
std::allocator 类模板是所有标准库容器所用的默认分配器 (Allocator) ,若不提供用户指定的分配器。默认分配器无状态,即任何给定的 allocator 实例可交换、比较相等,且能解分配同一 allocator 类型的任何其他实例所分配的内存。
//std::allocator,定义于头文件 <memory>
template< class T > struct allocator;
template<> struct allocator<void>; //特化,(C++17 中弃用)(C++20 中移除)
现在以std::deque容器为例,来探究一下std::allocator 的用处,首先它是用于为容器所有内存分配实现使用的,可处理容器对内存的分配与释放请求。C++的库的容器其共同特征之一,就是其大小可以在程序的运行时改变;为了实现这一点,进行动态内存分配就显得尤为必要,在此分配器就用于处理容器对内存的分配与释放请求。那么和emplace又有何关系呢
1.2 emplace原位构造元素
std::allocator支持可以移动赋值、构造、插入等操作方式或可复制赋值、构造、插入等操作方式,前者是原来的insert、push_back等处理方式,需要初始化新元素T 的副本,移动 T
进新元素,本质上是采用了operator new分配方式。后者为emplace、emplace_back等处理方式,用布置 new 于容器所提供的位置原位构造元素,即在预分配内存直接构造元素,采用的是std::allocator_traits<Alloc>::construct分配方式。
下来将看这个两种构建元素的方式在内存操作上如何。在这之前了解一下c++11标准里的原子操作的概念,标准库已经倾斜了对原子类型及原子操作的使用比例。
创建test.cpp源文件,输入:
void atomic_optest(void)
{int x = 10;int y = x;x++;y+=1;
}
上述函数的执行语句,那些是原子操作,那些不是呢。
通过汇编输出查看一下,编译命令如下:g++ -S test.cpp -o test.s,和cat test.s:
向一个执行语句只做了一次内存操作,可以认为是原子操作,而int y = x;显然不符合原子操作。
1.3 容器新旧两种修改器方式比对
下来再test.cpp中再定义一个函数:
#include <memory>
void emplace_stest(void)
{int *x = nullptr;std::allocator<int> alloc;alloc.construct(x,10);int *y = ::new int(10);//delete x;x = nullptr;delete y;y = nullptr;
}
再次执行g++ -S test.cpp -o test.s,和cat test.s编译命令如下,可以看出,在调用_ZNSaIiEC1Ev实现alloc定义后,后面给*x构造时,是直接赋值的原子操作,而通过operator new构造元素,在调用_ZNSt15__new_allocatorIiE9constructIiJiEEEvPT_DpOT0_后,还需要进行移动多次原子操作:
1.4 容器旧有修改器函数与emplace修改器效率比对
下来在看第三函数,测试std::deque容器的push_back、push_front、emplace_back、emplace_front的效率,通过1000*100000数据入队列执行步骤做效率比对:
//test.h
#ifndef _TEST_H_
#define _TEST_H_
void emplace_Test(void);
#endif //_TEST_H_//test.cpp
#include <queue>
#include <chrono>
#include <iostream>void emplace_Test(void)
{const unsigned long sizel = 100000; std::deque<int> dque;//push_backauto start = std::chrono::system_clock::now();for (size_t i = 0; i < 1000; i++)for (size_t i = 0; i < sizel; i++){dque.push_back(i%3);}auto end = std::chrono::system_clock::now();std::chrono::duration<double,std::milli> diff = end-start;std::cout << "push_back test diff.count() = " << diff.count() << "ms\n";dque.clear();//emplace_backstart = std::chrono::system_clock::now();for (size_t i = 0; i < 1000; i++)for (size_t i = 0; i < sizel; i++){dque.emplace_back(i%3);}end = std::chrono::system_clock::now();diff = end-start;std::cout << "emplace_back diff.count() = " << diff.count() << "ms\n";dque.clear();//push_frontstart = std::chrono::system_clock::now();for (size_t i = 0; i < 1000; i++)for (size_t i = 0; i < sizel; i++){dque.push_front(i%3);}end = std::chrono::system_clock::now();diff = end-start;std::cout << "push_front diff.count() = " << diff.count() << "ms\n";dque.clear();//emplace_frontstart = std::chrono::system_clock::now();for (size_t i = 0; i < 1000; i++)for (size_t i = 0; i < sizel; i++){dque.emplace_front(i%3);}end = std::chrono::system_clock::now();diff = end-start;std::cout << "emplace_front diff.count() = " << diff.count() << "ms\n";
};
//main.cpp
#include "test.h"int main(int argc, char* argv[])
{emplace_Test();return 0;
}
编译g++ main.cpp test.cpp -o test.exe -std=c++11,运行程序:
可以看到,采用emplace原位构造元素的方式在效率上是略占优势的,这也就是c++11主推emplace方式实现容器修改器的原因,为了保持标准的延续性,又保留了原有的push_back、push_front、insert等成员函数。
二、emplace对容器的支持
下来再次以std::deque看原位构造元素实现原理,std::deque容器提供了emplace、emplace_back、emplace_front三种修改器成员函数,以std::deque<T,Allocator>::emplace为例:
template< class... Args > iterator emplace( const_iterator pos, Args&&... args );
该函数和传统的insert函数实现同样的功能,直接于 pos 前插入元素到容器中。它是通过 std::allocator_traits::construct 构造元素,常用布置 new 在容器提供的位置原位构造元素。然而若要求的位置已被既存的元素占据,则首先在另一位置构造被插入的元素,然后再将他移动赋值到要求的位置中。将参数 args... 作为 std::forward<Args>(args)... 转发给构造函数。 args... 可以直接或间接地指代容器中的值。要注意的是:所有迭代器,含尾后迭代器,都被非法化。引用亦被非法化,除非 pos == begin() 或 pos == end() ,该情况下它们不被非法化。
2.1 容器emplace函数的用法
emplace的用法和insert是一致的,在指定位置前插入元素:
//test1.h
#ifndef _TEST_1_H_
#define _TEST_1_H_void emplace_data(void);#endif //_TEST_1_H_
//test1.cpp
#include "test1.h"#include <iostream>
#include <string>
#include <deque>struct A {std::string s;A(std::string str) : s(std::move(str)) { std::cout << " constructed\n"; }A(const A& o) : s(o.s) { std::cout << " copy constructed\n"; }A(A&& o) : s(std::move(o.s)) { std::cout << " move constructed\n"; }A& operator=(const A& other) {s = other.s;std::cout << " copy assigned\n";return *this;}A& operator=(A&& other) {s = std::move(other.s);std::cout << " move assigned\n";return *this;}
};void emplace_data(void)
{std::deque<A> container;std::cout << "construct 2 times A:\n";A two { "two" };A three { "three" };std::cout << "emplace with A&:\n";container.emplace(container.end(), two);std::cout << "emplace:\n";container.emplace(container.begin(), "one");std::cout << "emplace with A&&:\n";container.emplace(container.end(), std::move(three));std::cout << "emplace:\n";container.emplace(std::next(container.begin(),3), "four");std::cout << "content:\n";for (const auto& obj : container)std::cout << ' ' << obj.s;std::cout << '\n';
}
//main.cpp
#include "test1.h"int main(int argc, char* argv[])
{emplace_data();return 0;
}
emplace、emplace_back、emplace_front成员函数还支持next等迭代器操作符给出的指定位置插入数据:
//迭代器操作,定义于命名空间 std::experimental::ranges
advance //令迭代器前进给定的距离(函数模板)
distance //返回迭代器和哨位之间的距离,或范围起始和结尾间的距离(函数模板)
next //自增迭代器(函数模板)
prev //自减迭代器(函数模板)
2.2 emplace对不同容器的支持
针对不同的容器,emplace的成员操作略有不同:
/*相当于原来的insert*/
emplace //顺序容器、关联容器、无序关联容器、variant any
/*在容器中的指定位置后插入新元素。原位构造元素,即不进行复制或移动操作。*/
emplace_after //forward_list
/*相当于原来的push_back*/
emplace_back //deque list vector
/*相当于原来的push_front*/
emplace_front //deque list forward_list
/*插入新元素到容器中尽可能接近于恰在 hint 前的位置(建议性)。原位构造元素,即不进行复制或移动操作*/
emplace_hint //关联容器、无序关联容器
emplace_hint成员函数插入新元素到容器中尽可能接近于恰在 hint 前的位置。原位构造元素,即不进行复制或移动操作。参数args 为转发给元素构造函数的参数,以 std::forward<Args>(args)... 转发调用元素的构造函数。返回指向新插入元素的迭代器,而在因元素已存在而插入失败,则返回指向拥有等价关键的既存元素的迭代器。
template <class... Args> iterator emplace_hint( const_iterator hint, Args&&... args );
emplace_hint成员函数执行复杂度,通常与容器大小成对数,但若新元素正好被插入到 hint 之前则为均摊常数。
//test2.h
#ifndef _TEST_1_H_
#define _TEST_1_H_void emplace_hint_test();#endif //_TEST_1_H_
//test2.cpp
#include "test2.h"#include <chrono>
#include <iostream>
#include <iomanip>
#include <functional>#include <set>
#include <unordered_set>typedef std::set<int> myset;
// typedef std::unordered_set<int> myset;const int nof_operations = 10000000;int set_emplace_hint() {myset set;auto it = set.begin();for(int i = 0; i < nof_operations; ++i) {set.emplace_hint(it, i);it = set.end();}return set.size();
}int set_emplace_hint_wrong() {myset set;auto it = set.begin();for(int i = nof_operations; i > 0; --i) {set.emplace_hint(it, i);it = set.end();}return set.size();
}int set_emplace_hint_corrected() {myset set;auto it = set.begin();for(int i = nof_operations; i > 0; --i) {set.emplace_hint(it, i);it = set.begin();}return set.size();
}int set_emplace_hint_closest() {myset set;auto it = set.begin();for(int i = 0; i < nof_operations; ++i) {it = set.emplace_hint(it, i);}return set.size();
}void timeit(std::function<int()> set_test, std::string what = "") {auto start = std::chrono::system_clock::now();int setsize = set_test();auto stop = std::chrono::system_clock::now();std::chrono::duration<double, std::milli> time = stop - start;if (what.size() > 0 && setsize > 0) {std::cout << std::fixed << std::setprecision(2)<< time.count() << " ms for " << what << '\n';}
}void emplace_hint_test()
{timeit(set_emplace_hint, "emplace with correct hint");timeit(set_emplace_hint_wrong, "emplace with wrong hint");timeit(set_emplace_hint_corrected, "corrected emplace");timeit(set_emplace_hint_closest, "emplace using returned iterator");
};
//main.cpp
#include "test2.h"int main(int argc, char* argv[])
{emplace_hint_test();return 0;
}
编译测试g++ main.cpp test2.cpp -o test.exe -std=c++11,set_emplace_hint_wrong是尾部插入由大到小的数据,因此排序很耗时间:
采用无序关联容器unordered_set测试,调整一下代码:
// typedef std::set<int> myset;
typedef std::unordered_set<int> myset;
再次编译g++ main.cpp test2.cpp -o test.exe -std=c++11测试:
而对于无序容器,元素的插入效率几乎保持一致。
相关文章:

c/c++开发,无可避免的模板编程实践(篇十)-c++11原位构造元素(emplace)
一、容器修改器的新特性 c11以前,标准库的容器修改器功能提供了数据插入成员函数inset、push_back,而在 c11标准化,标准库的容器修改器增加了emplace、emplace_back、emplace_front等插入成员函数。同样是插入函数,两者有何区别呢…...

基于bash通过cdo批处理数据
***#################################### ubuntu中编写shell脚本文件 第一步:用vim创建一个以.sh结尾的文件,此时这个文件是暂时性的文件,当编写好文件并保存时才能看到文件; 第二步:要首先按一下“i”键才能进行插入…...

Map和Set总结
Map和Set Map和Set是专门用来进行搜索的数据结构,适合动态查找 模型 搜索的数据称为关键字(key),关键字对应的叫值(value),key-value键值对 key模型key-value模型 Map存储的就是key-value模型,Set只存储了key Map Map是接口类…...

pytorch网络模型构建中的注意点
记录使用pytorch构建网络模型过程遇到的点 1. 网络模型构建中的问题 1.1 输入变量是Tensor张量 各个模块和网络模型的输入, 一定要是tensor 张量; 可以用一个列表存放多个张量。 如果是张量维度不够,需要升维度, 可以先使用 …...

面试时候这样介绍redis,redis经典面试题
为什么要用redis做缓存 使用Redis缓存有以下几个优点: 1. 提高系统性能:缓存可以将数据存储在内存中,加快数据的访问速度,减少对数据库的读写次数,从而提高系统的性能。 2. 减轻后端压力:使用缓存可以减…...

机械学习 - scikit-learn - 数据预处理 - 2
目录关于 scikit-learn 实现规范化的方法详解一、fit_transform 方法1. 最大最小归一化手动化与自动化代码对比演示 1:2. 均值归一化手动化代码演示:3. 小数定标归一化手动化代码演示:4. 零-均值标准化(均值移除)手动与自动化代码演示&#x…...

华为OD机试题 - 最长连续交替方波信号(JavaScript)| 机考必刷
更多题库,搜索引擎搜 梦想橡皮擦华为OD 👑👑👑 更多华为OD题库,搜 梦想橡皮擦 华为OD 👑👑👑 更多华为机考题库,搜 梦想橡皮擦华为OD 👑👑👑 华为OD机试题 最近更新的博客使用说明本篇题解:最长连续交替方波信号题目输入输出示例一输入输出Code解题思路版…...

executor行为相关Spark sql参数源码分析
0、前言 参数名和默认值spark.default.parallelismDefault number of partitions in RDDsspark.executor.cores1 in YARN mode 一般默认值spark.files.maxPartitionBytes134217728(128M)spark.files.openCostInBytes4194304 (4 MiB)spark.hadoop.mapreduce.fileoutputcommitte…...

双通道5.2GSPS(或单通道10.4GSPS)射频采样FMC+模块
概述 FMC140是一款具有缓冲模拟输入的低功耗、12位、双通道(5.2GSPS/通道)、单通道10.4GSPS、射频采样ADC模块,该板卡为FMC标准,符合VITA57.1规范,该模块可以作为一个理想的IO单元耦合至FPGA前端,8通道的JE…...

理解java反射
是什么Java反射是Java编程语言的一个功能,它允许程序在运行时(而不是编译时)检查、访问和修改类、对象和方法的属性和行为。使用反射创建对象相比直接创建对象有什么优点使用反射创建对象相比直接创建对象的主要优点是灵活性和可扩展性。当我…...

EasyRcovery16免费的电脑照片数据恢复软件
电脑作为一种重要的数据储存设备,其中保存着大量的文档,邮件,视频,音频和照片。那么,如果电脑照片被删除了怎么办?今天小编给大家介绍,误删除的照片从哪里可以找回来,误删除的照片如…...

若依微服务版在定时任务里面跨模块调用服务
第一步 在被调用的模块中添加代理 RemoteTaskFallbackFactory.java: package com.ruoyi.rpa.api.factory;import com.ruoyi.common.core.domain.R; import com.ruoyi.rpa.api.RemoteTaskService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springf…...

SpringMVC简单配置
1、pom.xml配置 <dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.1.12.RELEASE</version></dependency></dependencies><build><…...

xcat快速入门工作流程指南
目录一、快速入门指南一、先决条件二、准备管理节点xcatmn.mydomain.com三、第1阶段:添加你的第一个节点并且用带外BMC接口控制它四、第 2 阶段 预配节点并使用并行 shell 对其进行管理二:工作流程指南1. 查找 xCAT 管理节点的服务器2. 在所选服务器上安…...

C++回顾(十九)—— 容器string
19.1 string概述 1、string是STL的字符串类型,通常用来表示字符串。而在使用string之前,字符串通常是 用char * 表示的。string 与char * 都可以用来表示字符串,那么二者有什么区别呢。 2、string和 char * 的比较 (1)…...

Hadoop入门
数据分析与企业数据分析方向 数据是什么 数据是指对可观事件进行记录并可以鉴别的符号,是对客观事物的性质、状态以及相互关系等进行记载的物理符号或这些物理符号的组合,它是可以识别的、抽象的符号。 他不仅指狭义上的数字,还可以是具有一…...

高校如何通过校企合作/实验室建设来提高大数据人工智能学生就业质量
高校人才培养应该如何结合市场需求进行相关专业设置和就业引导,一直是高校就业工作的讨论热点。亘古不变的原则是,高校设置不能脱离市场需求太远,最佳的结合方式是,高校具有前瞻性,能领先市场一步,培养未来…...

提升学习 Prompt 总结
NLP现有的四个阶段: 完全有监督机器学习完全有监督深度学习预训练:预训练 -> 微调 -> 预测提示学习:预训练 -> 提示 -> 预测 阶段1,word的本质是特征,即特征的选取、衍生、侧重上的针对性工程。 阶段2&…...

JavaScript学习笔记(2.0)
BOM--(browser object model) 获取浏览器窗口尺寸 获取可视窗口高度:window.innerWidth 获取可视窗口高度:window.innerHeight 浏览器弹出层 提示框:window.alert(提示信息) 询问框:window.confirm(提示信息) 输…...

直击2023云南移动生态合作伙伴大会,聚焦云南移动的“价值裂变”
作者 | 曾响铃 文 | 响铃说 2023年3月2日下午,云南移动生态合作伙伴大会在昆明召开。云南移动党委书记,总经理葛松海在大会上提到“2023年,云南移动将重点在‘做大平台及生态级新产品,做优渠道转型新动能,做强合作新…...

STM32F1开发实例-振动传感器(机械)
振动(敲击)传感器 振动无处不在,有声音就有振动,哒哒的脚步是匆匆的过客,沙沙的夜雨是暗夜的忧伤。那你知道理科工程男是如何理解振动的吗?今天我们就来讲一讲本节的主角:最简单的机械式振动传感器。 下图即为振动传…...

2023最新ELK日志平台(elasticsearch+logstash+kibana)搭建
去年公司由于不断发展,内部自研系统越来越多,所以后来搭建了一个日志收集平台,并将日志收集功能以二方包形式引入自研系统,避免每个自研系统都要建立一套自己的日志模块,节约了开发时间,管理起来也更加容易…...

2023-3-10 刷题情况
打家劫舍 IV 题目描述 沿街有一排连续的房屋。每间房屋内都藏有一定的现金。现在有一位小偷计划从这些房屋中窃取现金。 由于相邻的房屋装有相互连通的防盗系统,所以小偷 不会窃取相邻的房屋 。 小偷的 窃取能力 定义为他在窃取过程中能从单间房屋中窃取的 最大…...

如何建立一个成功的MES?
制造执行系统(MES)是一种为制造业企业提供实时生产过程控制、管理和监视的信息系统。一个成功的MES系统可以帮助企业提高生产效率,降低成本,提高产品质量,提高客户满意度等。下面是一些关键步骤来建立一个成功的MES系统…...

Kafka生产者幂等性/事务
Kafka生产者幂等性/事务幂等性事务Kafka 消息交付可靠性保障: Kafka 默认是:至少一次最多一次 (at most once) : 消息可能会丢失,但绝不会被重复发送至少一次 (at least once) : 消息不会丢失,但有可能被重复发送精确一次 (exact…...

JavaWeb--案例(Axios+JSON)
JavaWeb--案例(AxiosJSON)1 需求2 查询所有功能2.1 环境准备2.2 后端实现2.3 前端实现2.4 测试3 添加品牌功能3.1 后端实现3.2 前端实现3.3 测试1 需求 使用Axios JSON 完成品牌列表数据查询和添加。页面效果还是下图所示: 2 查询所有功能 …...

css制作动画(动效的序列帧图)
相信 animation 大家都用过很多,知道是 CSS3做动画用的。而我自己就只会在 X/Y轴 上做位移旋转,使用 animation-timing-function 规定动画的速度曲线,常用到的 贝塞尔曲线。但是这些动画效果都是连续性的。 今天发现个新功能 animation-timi…...

【设计模式】装饰器模式
装饰器模式 以生活中的场景来举例,一个蛋糕胚,给它涂上奶油就变成了奶油蛋糕,再加上巧克力和草莓,它就变成了巧克力草莓蛋糕。 像这样在不改变原有对象的基础之上,将功能附加到原始对象上的设计模式就称为装饰模式(D…...

Nginx配置实例-反向代理案例一
实现效果:使用nginx反向代理,访问 www.suke.com 直接跳转到本机地址127.0.0.1:8080 一、准备工作 Centos7 安装 Nginxhttps://liush.blog.csdn.net/article/details/125027693 1. 启动一个 tomcat Centos7安装JDK1.8https://liush.blog.csdn.net/arti…...

Java中IO流中字节流(FileInputStream(read、close)、FileOutputStream(write、close、换行写、续写))
IO流:存储和读取数据的解决方案 纯文本文件:Windows自带的记事本打开能读懂 IO流体系: FileInputStream:操作本地文件的字节输入流,可以把本地文件中的数据读取到程序中来 书写步骤:①创建字节输入流对象 …...