C++中的五种高级初始化技术:从reserve到piecewise_construct等

C++高级初始化技术:reserve、emplace_back、constinit、Lambda表达式、piecewise_construct

  • 一、简介
  • 二、reserve 结合 emplace_back
  • 三、C++ 20的constinit
  • 四、Lambda表达式和初始化
  • 五、make_unique_for_overwrite
  • 六、piecewise_construct 和 forward_as_tuple
  • 七、总结

一、简介

从动态容器操作到编译时常量,C++提供了多种技术。在这篇文章中,将深入研究高级初始化方法,如reserve()emplace_back,以及使用 piecewise_constructforward_as_tuple 构建元组。由于这些技术,我们可以减少临时对象的数量,并更有效地创建变量。

在讲解高级初始化技术之前,我们先看一个前置背景,使用下面的类来方便地说明何时调用它的特殊成员函数。这样,我们就能看到额外的临时对象。

struct MyType {MyType() { std::cout << "MyType default\n"; }explicit MyType(std::string str) : str_(std::move(str)) { std::cout << std::format("MyType {}\n", str_); }~MyType() { std::cout << std::format("~MyType {}\n", str_);  }MyType(const MyType& other) : str_(other.str_) { std::cout << std::format("MyType copy {}\n", str_); }MyType(MyType&& other) noexcept : str_(std::move(other.str_)) { std::cout << std::format("MyType move {}\n", str_);  }MyType& operator=(const MyType& other) { if (this != &other)str_ = other.str_;std::cout << std::format("MyType = {}\n", str_);  return *this;}MyType& operator=(MyType&& other) noexcept { if (this != &other)str_ = std::move(other.str_);std::cout << std::format("MyType = move {}\n", str_);  return *this; }std::string str_;
};

现在我们可以从相对简单但基本的元素开始。

二、reserve 结合 emplace_back

C++中的vector是一种可以根据需要增长的动态数组。但是,每次向量增长超过其当前容量时,它可能需要重新分配内存,这代价是昂贵的。为了优化这一点,可以使用reserve()方法结合emplace_back()

reserve方法不改变vector的大小,但确保vector有足够的已分配内存来存储指定数量的元素。通过提前预留空间,可以防止在向向量添加元素时进行多次重新分配。

示例:

#include <iostream>
#include <string>
#include <vector>
#include <format>struct MyType {MyType() { std::cout << "MyType default\n"; }explicit MyType(std::string str) : str_(std::move(str)) { std::cout << std::format("MyType {}\n", str_); }~MyType() { std::cout << std::format("~MyType {}\n", str_);  }MyType(const MyType& other) : str_(other.str_) { std::cout << std::format("MyType copy {}\n", str_); }MyType(MyType&& other) noexcept : str_(std::move(other.str_)) { std::cout << std::format("MyType move {}\n", str_);  }MyType& operator=(const MyType& other) { if (this != &other)str_ = other.str_;std::cout << std::format("MyType = {}\n", str_);  return *this;}MyType& operator=(MyType&& other) noexcept { if (this != &other)str_ = std::move(other.str_);std::cout << std::format("MyType = move {}\n", str_);  return *this; }std::string str_;
};int main() {    {std::cout << "--- push_back\n";std::vector<MyType> vec;vec.push_back(MyType("First"));std::cout << std::format("capacity: {}\n", vec.capacity());vec.push_back(MyType("Second"));}{std::cout << "--- emplace_back\n";std::vector<MyType> vec;vec.emplace_back("First");std::cout << std::format("capacity: {}\n", vec.capacity());vec.emplace_back("Second");}{std::cout << "--- reserve() + emplace_\n";std::vector<MyType> vec;vec.reserve(2);  // Reserve space for 2 elementsvec.emplace_back("First");vec.emplace_back("Second");}
}

输出:

--- push_back
MyType First
MyType move First
~MyType 
capacity: 1
MyType Second
MyType move Second
MyType move First
~MyType 
~MyType 
~MyType First
~MyType Second
--- emplace_back
MyType First
capacity: 1
MyType Second
MyType move First
~MyType 
~MyType First
~MyType Second
--- reserve() + emplace_
MyType First
MyType Second
~MyType First
~MyType Second

在这个例子中,可以看到三种插入技术之间的比较:

  • push_back()
  • emplace_back()
  • reserve()结合emplace_back

第一种情况下,必须将临时对象传递给push_back,并移动它们来初始化vector的元素。但是还有一个重新分配,因为当添加第二个元素时,vector必须增长。

相对而言,emplace_back()技术更好,更容易编写,因为没有创建临时对象。

但是,第三种选择是最有效的,因为可以预先预留空间,然后在适当的地方创建元素。

通过使用reserveemplace_back,可以确保vector在添加元素到预留容量时不需要重新分配内存。这种组合是优化性能的一种强大方式,特别是在向vector`中添加多个元素时。

三、C++ 20的constinit

constinit是一个强大的工具,用于强制常量初始化,特别是对于静态或线程局部变量。这个关键字在C++ 20中引入,它解决了C++中一个长期存在的难题:静态初始化顺序的问题。通过确保变量在编译时初始化,constinit提供了一个更可预测和更安全的初始化过程。

在其核心,constinit保证它所限定的变量在编译时被初始化。这对全局变量或静态变量尤其有益,可以确保它们不受动态初始化顺序问题的影响。

示例:

#include <array>// 编译期初始化
constexpr int compute(int v) { return v*v*v; }
constinit int global = compute(10);// 这个将不再起作用:
// constinit int another = global;int main() {// 但允许以后更改……global = 100;// global is not constant!// std::array<int, global> arr;
}

在上面的代码中,全局变量是在编译时使用compute函数初始化的。然而,与constconstexpr不同,constinit不会使变量不可变。也就是说,虽然它的初始值是在编译时设置的,但可以在运行时对其进行修改,如main函数所示。此外,由于constinit变量不是constexpr,因此不能使用它初始化另一个constinit对象(如其他的int)。

四、Lambda表达式和初始化

C++ 14对Lambda捕获进行了重大更新,引入了在Lambda捕获子句中直接初始化新数据成员的能力。这个特性称为带有初始化器的捕获或广义Lambda捕获,它在使用Lambda时为我们提供了更大的灵活性和精度。

传统上,Lambda表达式可以从其封闭范围捕获变量。在C++ 14中,现在可以直接在捕获子句中创建和初始化新的数据成员,使Lambdas更加通用。

示例:

#include <iostream>int main() {int x = 30;int y = 12;const auto foo = [z = x + y]() { std::cout << z; };x = 0;y = 0;foo();
}

输出:

42

在这里,创建了一个新的数据成员z,并用xy的和进行初始化。这个初始化发生在Lambda定义点,而不是调用点。因此,即使在Lambda定义之后修改了xy, z仍然保持其初始值。

为了更好地理解这个特性,来看看Lambda是如何转换为可调用类型的:

struct _unnamedLambda {void operator()() const {std::cout << z;}int z;
} someInstance;

Lambda本质上变成了一个带有operator()()方法和数据成员z的未命名结构的实例。

使用初始化器捕获不仅限于简单类型,还可以捕获引用。

这种技术在什么情况会很方便呢?至少有两种情况:

  • 捕获只能移动的类型,特别是通过值。
  • 优化。

考虑第一种情况,下面是捕获std::unique_ptr的方法:

#include <iostream>
#include <memory>int main(){std::unique_ptr<int> p(new int{10});const auto bar = [ptr=std::move(p)] {std::cout << "pointer in lambda: " << ptr.get() << '\n';};std::cout << "pointer in main(): " << p.get() << '\n';bar();
}

在以前的C++ 11中,不能按值捕获惟一的指针,只可能通过引用捕获。现在,从C++ 14开始,可以将对象移动到闭包类型的成员中。

另一个情况是优化。比如,如果捕获一个变量,然后计算一些临时对象:

auto result = std::find_if(vs.begin(), vs.end(),[&prefix](const std::string& s) {return s == prefix + "bars"; });

为什么不计算一次并将其存储在lambda对象中呢?

result = std::find_if(vs.begin(), vs.end(), [savedString = prefix + "bars"](const std::string& s) { return s == savedString; });

这样,savedString只计算一次,而不是每次调用函数时都计算一次。

五、make_unique_for_overwrite

通过使用智能指针,获得了能够显著降低与动态内存分配相关的风险的工具。但是,和所有任何工具一样,总是有改进和优化的空间。

当使用make_unique(或make_shared)分配数组时,默认行为是对每个元素进行值初始化。这意味着对于内置类型,每个元素都设置为零,对于自定义类型,调用其默认构造函数。虽然这确保了内存被初始化为已知状态,但它带来了性能开销,特别是当想要立即覆盖已分配的内存时。

示例:

auto ptr = std::make_unique<int[]>(1000); 

这一行不仅为1000个整数分配内存,而且还将每个整数初始化为零。如果下一步是用来自文件或网络操作的数据填充该内存,那么初始归零是不必要的,也是浪费的。

为了解决这种低效率问题,C++ 20引入了make_unique_for_overwritemake_shared_for_overwrite。这些函数分配内存时不需要对其进行值初始化,这使得它们在直接打算覆盖内存时更快。

auto ptr = std::make_unique_for_overwrite<int[]>(1000);

当分配的内存立即被其他数据覆盖时,*_for_overwrite函数是最有用的。但是要注意,如果内存没有被覆盖,它包含不确定的值,这时如果访问的话可能导致未定义的行为。

这些新功能可以显著提高执行大量内存操作的应用程序的性能,例如数据处理工具或游戏引擎。

六、piecewise_construct 和 forward_as_tuple

最后,让我们看看第五种技术:使用多参数构造函数直接初始化对或元组。这就是std::piecewise_constructstd::forward_as_tuple发挥作用的地方。

示例:

std::pair<MyType, MyType> p { "one", "two" };

上面的代码创建了没有额外临时MyType对象的pair

但是,如果有一个额外的构造函数接受两个参数,那该怎么办呢?

MyType(std::string str, int a)

在这种情况下,如果这样:

std::pair<MyType, MyType> p { "one", 1, "two", 2 };

毫无疑问,它失败了,因为该调用对编译器是二义性的。

在这些情况下,std::piecewise_construct可以提供帮助。它是一个指示std::pair执行分段构造的标记。当与std::forward_as_tuple(创建左值或右值引用的元组)结合使用时,可以将多个参数转发给pair元素的构造函数。

#include <iostream>
#include <string>
#include <format>struct MyType {MyType() { std::cout << "MyType default\n"; }explicit MyType(std::string str) : str_(std::move(str)) { std::cout << std::format("MyType {}\n", str_); }MyType(std::string str, int a) : str_(std::move(str)) { std::cout << std::format("MyType {}, {}\n", str_, a); }~MyType() { std::cout << std::format("~MyType {}\n", str_);  }MyType(const MyType& other) : str_(other.str_) { std::cout << std::format("MyType copy {}\n", str_); }MyType(MyType&& other) noexcept : str_(std::move(other.str_)) { std::cout << std::format("MyType move {}\n", str_);  }MyType& operator=(const MyType& other) { if (this != &other)str_ = other.str_;std::cout << std::format("MyType = {}\n", str_);  return *this;}MyType& operator=(MyType&& other) noexcept { if (this != &other)str_ = std::move(other.str_);std::cout << std::format("MyType = move {}\n", str_);  return *this; }std::string str_;
};int main() {{std::cout << "regular: \n";std::pair<MyType, MyType> p { MyType{"one", 1}, MyType{"two", 2}};}{std::cout << "piecewise + forward: \n";std::pair<MyType, MyType>p2(std::piecewise_construct,std::forward_as_tuple("one", 1),std::forward_as_tuple("two", 2));}
}

运行这个程序会看到以下输出:

regular: 
MyType one, 1
MyType two, 2
MyType move one
MyType move two
~MyType 
~MyType 
~MyType two
~MyType one
piecewise + forward: 
MyType one, 1
MyType two, 2
~MyType two
~MyType one

可以看到,常规方法创建了两个临时对象。而 使用分段选项可以直接将参数传递给pair的元素。

std::piecewise_construct对于像std::mapstd::unordered_map这样存储键值对(std::pair)的容器特别有用。当想要向这些容器中插入元素,并且键或值(或两者)具有多参数构造函数或不可复制时,std::piecewise_construct的实用程序变得很方便。

示例:

#include <string>
#include <map>struct Key {Key(int a, int b) : sum(a + b) {}int sum;bool operator<(const Key& other) const { return sum < other.sum; }
};struct Value {Value(const std::string& s, double d) : name(s), data(d) {}std::string name;double data;
};int main() {std::map<Key, Value> myMap;// doesn't compile: ambiguous// myMap.emplace(3, 4, "example", 42.0);// works:myMap.emplace(std::piecewise_construct,std::forward_as_tuple(3, 4),  std::forward_as_tuple("example", 42.0) );
}

七、总结

本文探讨了初始化C++代码的各种技术。深入研究了现代C++特性的复杂性,包括reserveemplace_back的效率、constinit的准确性和lambda初始化的灵活性。此外,还研究了piecewise forward_as_tuple的细微差别。这些高级技术展示了c++语言的发展和强大,并为开发人员提供了编写更具表现力、更高效和更通用的代码的能力。

有些人可能会认为这是语言中不必要的复杂,这不是一定的。考虑emplace()函数,它可以改进容器插入。但是,如果不需要优化,可以使用更简单的代码来传递临时对象。

所提供的高级技术列表可能并不详尽。博主也非常好奇是否还有其他更有效但更有挑战性的初始化对象的有用技术。可以在评论区分享你的想法。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/2514.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot xxl-job 任务调度

首先官网下载xxl-job的源代码&#xff0c;然后切换到jdk8&#xff0c;等Maven下载依赖 执行mysql的脚本&#xff0c;修改连接配置&#xff0c;启动admin站点 默认地址 http://localhost:8080/xxl-job-admin/ 先新增一个任务执行器&#xff0c;指向未来任务代码的站点 然后在…

Vue.js应用中的多元化通信策略:10+种方法深度解析

Vue.js应用中的多元化通信策略&#xff1a;10种方法深度解析 在构建复杂且交互丰富的Vue 2.x应用程序时&#xff0c;有效的组件间通信是确保数据流通、状态同步与逻辑协调的关键。本文将深入探讨超过10种适用于Vue 2.x的应用内通信方法&#xff0c;覆盖父子组件、兄弟组件、跨…

探索亚马逊云科技「生成式 AI 精英速成计划」

目录 前言「生成式 AI 精英速成计划」技术开发课程学习课程学习 总结 前言 亚马逊云科技&#xff08;Amazon Web Services&#xff0c;简称AWS&#xff09;作为全球领先的云计算服务提供商&#xff0c;一直以来在推动人工智能&#xff08;AI&#xff09;领域的发展中扮演着重要…

04_c/c++开源库 json解析jsoncpp库

1.说明与安装 说明: c json字符解析 安装: sudo apt-get install libjsoncpp-dev 编译依赖 pkg-config --cflags --libs jsoncpp -I/usr/include/jsoncpp -ljsoncpp 编译选项: -I/usr/include/jsoncpp 连接选项: -ljsoncpp 2.实例 1.代码 1_jsonCpp_解析字符串_增.删.改…

IContentService

目录 1、 IContentService 1.1、 // * 保存文章 1.2、 * 发布文章 1.3、 *查询文章返回多条数据 1.4、 * 根据id或slug获取文章 1.5、 * 查询分类/标签下的文章归档 2、 IRelationshipService

Gitea:轻量级、开源的Git仓库管理平台

引言 Gitea是一款开源的、基于Go语言编写的轻量级Git服务器。它提供了类似于GitHub的功能&#xff0c;如代码托管、版本控制、团队协作等&#xff0c;但更加轻便和易于部署。Gitea的设计初衷是为了让团队或个人能够更方便地管理和分享自己的代码&#xff0c;同时不需要花费大量…

MATLAB将多张小图整合到一张大图形成模板图

MATLAB将多张小图整合到一张大图形成模板图 代码如下: clc;close all;clear all;warning off;%清除变量 rand(seed, 100); randn(seed, 100); format long g;foldername字符模板; [datacell,filenamecell,filenameAllcell]readfun_1n(foldername); K2length(filenamecell);% …

常见射频指标的本质和意义(一)

1、Rx Sensitivity&#xff08;接收灵敏度&#xff09; 接收灵敏度&#xff0c;这应该是最基本的概念之一&#xff0c;表征的是接收机能够在不超过一定误码率的情况下识别的最低信号强度。这里说误码率&#xff0c;是沿用CS&#xff08;电路交换&#xff09;时代的定义作一个通…

【YOLO改进】主干插入SKAttention模块(基于MMYOLO)

SKAttention模块 论文链接:https://arxiv.org/pdf/1903.06586.pdf 将SKAttention模块添加到MMYOLO中 将开源代码SK.py文件复制到mmyolo/models/plugins目录下 导入MMYOLO用于注册模块的包: from mmyolo.registry import MODELS 确保 class SKAttention中的输入维度为in_cha…

读天才与算法:人脑与AI的数学思维笔记08_生物的创造力

1. 生物的创造力 1.1. 在进化树中是否有其他的物种已经具有与我们人类相当的创造力水平 1.2. 20世纪50年代中期&#xff0c;动物学家德斯蒙德莫里斯&#xff08;Desmond Morris&#xff09;在伦敦动物园做了这样一个试验 1.2.1. 动物学家给…

Laravel 6 - 第十四章 响应

​ 文章目录 Laravel 6 - 第一章 简介 Laravel 6 - 第二章 项目搭建 Laravel 6 - 第三章 文件夹结构 Laravel 6 - 第四章 生命周期 Laravel 6 - 第五章 控制反转和依赖注入 Laravel 6 - 第六章 服务容器 Laravel 6 - 第七章 服务提供者 Laravel 6 - 第八章 门面 Laravel 6 - …

《ESP8266通信指南》4-以Client进行TCP通信(AT指令)

往期 《ESP8266通信指南》3-常用AT指令详解-8266连WIFI-CSDN博客 《ESP8266通信指南》2-ESP8266 AT测试-CSDN博客 《ESP8266通信指南》1-ESP8266 简介-CSDN博客 1. 小节目标 通过 AT 指令使用 8266 进行 TCP 通信 2. 书接上回 复习以下&#xff0c;上一小节我们讲到了 8…

学习java第五十天

Spring框架中的Bean的生命周期是什么&#xff1f; 在Spring中&#xff0c;Bean的生命周期可以被划分为以下阶段&#xff1a; 实例化&#xff1a;在这个阶段中&#xff0c;Spring容器根据Bean的定义&#xff0c;通过反射或其他方法来创建Bean的实例。这个阶段包括调用构造方法和…

【MongoDB】--MongoDB的组合索引

目录 一、前言二、Query查询条件转换shell输入命令1、常用shell输入命令2、explain()解析计划三、组合索引的说明一、前言 本文章主要介绍Mongodb的组合索引的使用。 二、Query查询条件转换shell输入命令 1、常用shell输入命令 Query: {"tenantsid": {"$num…

hyperf 三十一 极简DB组件

一 安装及配置 composer require hyperf/db php bin/hyperf.php vendor:publish hyperf/db 默认配置 config/autoload/db.php 如下&#xff0c;数据库支持多库配置&#xff0c;默认为 default。 配置项类型默认值备注driverstring无数据库引擎 支持 pdo 和 mysqlhoststringl…

算法(哈希表

给你两个字符串&#xff1a;ransomNote 和 magazine &#xff0c;判断 ransomNote 能不能由 magazine 里面的字符构成。 如果可以&#xff0c;返回 true &#xff1b;否则返回 false 。 magazine 中的每个字符只能在 ransomNote 中使用一次。 示例 1&#xff1a; 输入&#…

如何搭建邮箱服务器?mail系统架设的两种方法

邮件mail通信是常用的办公场景&#xff0c;对于技术和网管等人员&#xff0c;往往需要搭建自己的邮箱服务器。那么&#xff0c;如何架设邮箱系统呢&#xff1f;通常有两种方案&#xff0c;一种是在在本地主机部署&#xff0c;另一种是在云端如云服务器上部署应用。根据主机IP情…

立即刷新导致请求的response没有来得及加载造成的this request has no response data available

1、前端递归调用后端接口 const startProgress () > {timer.value setInterval(() > {if (progress.value < 100) {time.value--;progress.value Math.ceil(100 / wait_time.value);} else {clearInterval(timer.value);progress.value 0;timer.value null;time.…

暴力数据结构之单链表专题

1. 单链表的初始化 首先定义节点的结构&#xff0c;然后动态内存申请一部分空间&#xff0c;每一个节点都有一个值以及指向下一个节点的指针&#xff0c;称作值域和指针域。 //定义节点的结构 //数据 指向下一个节点的指针typedef int SLTDataType;typedef struct SListNode…

40. UE5 RPG给火球术增加特效和音效

前面&#xff0c;我们将火球的转向和人物的转向问题解决了&#xff0c;火球术可以按照我们的想法朝向目标发射。现在&#xff0c;我们解决接下来的问题&#xff0c;在角色释放火球术时&#xff0c;会产生释放音效&#xff0c;火球也会产生对应的音效&#xff0c;在火球击中目标…