3个令人惊叹的C++17功能,让您的代码变得更简洁

本文介绍了C++17中的三个关键特性:结构化绑定、模板参数推导和选择初始化,展示了它们如何使代码更简洁、易读和减少错误。
摘要由CSDN通过智能技术生成

一、简介

C++17 为 C++ 语言带来了许多功能。本文深入研究其中的三个,它们有助于使编码更容易、更简洁、更直观和正确。

本文将从 结构化绑定 开始。结构化绑定适用于许多情况,本文中将介绍几种情况可以使代码更简洁、更简单。接着介绍 模板参数推导,它允许删除习惯于键入的模板参数。最后介绍使用“选择初始化”使我们能够更好地控制对象范围,并允许定义它们所属的值。

二、结构化绑定

结构化绑定允许一次性定义多个对象,其方式比以前的 C++ 版本更自然。
从 C++11 到 C++17,这个概念本身并不新鲜。C++17以前,始终可以从函数返回多个值并使用std::tie

例如:

std::tuple<char, int, bool> mytuple()
{
    char a = 'a';
    int i = 123;
    bool b = true;
    return std::make_tuple(a, i, b);
}

这将返回三个不同类型的变量。要从 C++17 之前调用函数访问这些函数,需要如下内容:

char a;
int i;
bool b;

std::tie(a, i, b) = mytuple();

其中必须在使用前定义变量并预先了解类型。

但是使用结构化绑定,可以简单地将其执行为:

auto [a, i, b] = mytuple();

这是一种更好的语法,几乎尽可能使用auto,与现代C++样式也一致。

那么,什么可以与结构化绑定初始化一起使用呢?基本上任何复合类型structpairtuple。让我们看看它可能有用的几种情况。

2.1、返回复合对象

这是一次性将复合类型的各个部分(如structpair等)分配给不同变量并自动分配正确类型的简单方法。例如,如果把数据插入到std::map中,那么结果是一个:std::pair

std::map<char,int> mymap;
auto mapret = mymap.insert(std::pair('a', 100));

可能有人想知道上面的代码为什么没有明确说明配对的类型,这就是后面要讲的 C++17 中的模板参数推导 了,请接着往下阅读。因此,为了确定数据插入是否成功,可以从插入方法返回的内容mapret中提取second信息。

问题在于,读者应该需要查找什么才能确定是否插入成功。但是使用结构化绑定,这将变成:

auto [itelem, success] = mymap.insert(std::pair(’a’, 100));
If (!success) {
    // Insert failure
}

其中itelem是元素的迭代器,success 的类型为 booltrue用于表示插入成功。变量的类型是从赋值中自动推断出来的——这在阅读代码时更有意义。

选择初始化 作为本文最后一节的内容,这里先睹为快,由于 C++17 现在具有选择初始化,那么我们可以将其编写为:

if (auto [itelem, success] = mymap.insert(std::pair(‘a’, 100)); success) {
    // Insert success
}

这个稍后会详细介绍。

2.2、遍历复合集合

结构化绑定也适用于范围。因此,考虑到前面的 mymap 定义,在 C++17 之前是使用如下所示的代码对其进行迭代:

for (const auto& entry : mymap) {
    // Process key as entry.first
    // Process value as entry.second
}

或者,更明确地说:

for (const auto& entry : mymap) {
    auto& key = entry.first;
    auto& value = entry.second;
    // Process entry
}

但是结构化绑定允许更直接地编写它:

for (const auto&[key, value] : mymap) {
    // Process entry using key and value
}

变量keyvalue的用法比entry.firstentry.second更具指导意义,并且不需要额外的变量定义。

2.3、直接初始化

但是,由于结构化绑定可以从tuplepair等进行初始化,我们可以以这种方式进行直接初始化吗?

是的,我们能。比如:

auto a = ‘a’;
auto i = 123;
auto b = true;

它将变量a的初始值定义为char 类型的ai 初始值为int 类型的 123 ,以及b初始值为 bool 的类型的true

使用结构化绑定,可以写成:

auto [a, i, b] = tuple(‘a’, 123, true);    // With no types needed for the tuple!

这将变量aib 定义得就像使用了上面的单独定义一样。

这真的是对之前定义的改进吗?已经在一行中完成了本来需要三行才能完成的工作,但为什么要这样做呢?考虑以下代码:

{
    istringstream iss(head);
    for (string name; getline(iss, name); )
    // Process name
}

issname两者都只在 for 块内使用,但必须在 for 语句之外和它自己的块内声明,以便将范围限制在所需的范围内。

这很奇怪,因为 iss 是属于 for 循环。始终可以初始化相同类型的多个变量。例如:

for (int i = 0, j = 100; i < 42; ++i, --j) {
    // Use i and j
}

但我们想写但不能写的是:

for (int i = 0, char ch = ‘ ‘; i < 42; ++i) {    // Does not compile
    // Use i and ch
}

使用结构化绑定,我们就可以这样编写:

for (auto[iss, name] = pair(istringstream(head), string {}); getline(iss, name); ) {
    // Process name
}

for (auto[i, ch] = pair(0U, ‘ ‘); i < 42; ++i) {  // The 0U makes i an unsigned int
    // Use i and ch
}

这允许根据需要在 for 语句的范围内定义变量 issname(以及ich),并且还可以自动确定它们的类型。

ifswitch语句也是如此,它现在在 C++ 17 中采用可选的选择初始化(见下文)。例如:

if (auto [a, b] = myfunc(); a < b) {
    // Process using a and b
}

注意,并不能用结构化绑定做所有事情,试图让它们适应每种情况会使代码更加复杂。比如:

if (auto [box, bit] = std::pair(std::stoul(p), boxes.begin()); (bit = boxes.find(box)) != boxes.end()){
    // Process if using both box and bit variables
}

此处box变量被定义为类型 unsigned long,并且具有从stoul(p)返回的初始值。对于那些不熟悉它的人来说,stoul()是一个函数,它以类型作为其第一个参数(还有其他可选参数<string> - 包括 std::string_base),并将其内容解析为指定基数的整数(默认为 10),该函数作为无符号长整型值返回。

变量的类型是迭代器的类型,bit初始值为boxes.begin()bit这只是为了确定其auto 的类型。变量的实际值是在 if 语句的条件测试部分设置的。这突出显示了以这种方式使用结构化绑定的约束。我们真正想写的是:

if (const auto [box, bit] = std::pair(std::stoul(p), boxes.find(box)); bit != boxes.end()){
    // This doesn’t compile
    // Process if using both box and bit variables
}

但是不能这样,因为在auto类型说明符中声明的变量不能出现在它自己的初始值设定项中!这是可以理解的。

综上所述,使用结构化绑定的优点是:

  • 声明一个或多个局部变量的单个声明。
  • 可以有不同的类型。
  • 其类型始终使用单个自动来推断。
  • 从复合类型分配。

当然,缺点是使用了中介(例如std::pair)。这不一定会影响性能(无论如何,它只在循环开始时执行一次),因为将尽可能使用移动语义。但需要注意,如果使用的类型是不可移动的(例如,std::array),那么这可能会产生性能“hit”,具体取决于复制操作涉及的内容。

不要预先判断编译器和预先优化代码!如果性能不符合要求,则使用分析器查找瓶颈,否则就是在浪费开发时间。只需编写最简单、最干净的代码即可。

三、模板参数推导

模板参数推导是模板化类在不明确说明类型的情况下确定构造函数传递的参数类型的能力。

在 C++17 之前,要构造模板化类的实例,必须显式声明参数的类型(或使用其中一个支持函数make_xyz)。

比如:

std::pair<int, double> p(2, 4.5);

这里是p类的实例,并使用 2 和 4.5 进行初始化。或者实现此目的的另一种方法是:

auto p = std::make_pair(2, 4.5);

这两种方法都有各自的缺点。创建“make 函数”是不清楚的、人为的,并且与非模板类的构造方式不一致。std::make_pairstd::make_tuple等在标准库中可用,但对于用户定义的类型来说,情况更糟:必须编写自己的make_......功能。

指定模板参数,如下所示:

auto p = std::pair<int, double>(2, 4.5)

应该是不必要的,因为它们可以从参数的类型中推断出来——就像模板函数一样。

因此,在 C++17 中,已取消指定模板化类构造函数类型的要求。这意味着现在可以这样写:

auto p = std::pair(2, 4.5);
// 或
std::pair p(2, 4.5);

这是期望能够定义的逻辑方式!

所以,前面的mytuple()函数可以使用模板参数推导(以及函数返回类型的auto),可以考虑:

auto mytuple()
{
    char a = 'a';
    int i = 123;
    bool b = true;
    return std::tuple(a, i, b);  // No types needed
}

这是一种更简洁的编码方式。在这种情况下,甚至可以将其包装为:

auto mytuple()
{
    return std::tuple(‘a’, 123, true);  // Auto type deduction from arguments
}

四、选择初始化

选择初始化允许在ifswitch语句中进行可选的变量初始化,类似于 for 语句中使用的初始化。例如:

for (int a = 0; a < 10; ++a) {
    // for body
}

此处a的范围仅限于 for 语句。但下面的代码:

{
    auto a = getval();
    if (a < 10) {
    // Use a
    }
}

这里变量a仅在 if 语句中使用,但如果想限制其范围,则必须在其自己的块中定义变量。在 C++17 中,这可以写成:

if (auto a = getval(); a < 10) {
    // Use a
}

它遵循与 for 语句相同的初始化语法:初始化部分与选择部分用分号;分隔。同样的初始化语法也可以与 switch 语句一起使用。例如:

switch (auto ch = getnext(); ch) {
    // case statements as needed
}

这一切都很好地帮助C++变得更加简洁,直观和正确!有多少人编写过这样的代码:

int a;
if ((a = getval()) < 10) {
    // Use a
}

...

// Much further on in the code – a has the same value as previously

if (a == b) {
    //...
}

在测试之前,第二个a没有正确初始化(错误),但由于早期的定义而没有被编译器拾取。这仍在范围内,因为它没有在自己的if块中定义。如果这在 C++17 中,可以这样写:

if (auto a = getval(); a < 10) {
    // Use a
}

... // Much further on in the code - a is not now defined

if (a == b) {
    // ...
}

然后,这将被编译器拾取并报告为错误。修复编译器错误的成本远低于未知运行时问题!

五、总结

已经了解了结构化绑定如何允许单个声明声明一个或多个局部变量,这些变量可以具有不同的类型,并且其类型始终使用单个auto,它们可以从复合类型中分配。

模板参数推导允许避免编写冗余的模板参数和辅助函数来推导它们。选择初始化使 ifswitch 语句中的初始化与 for 语句一致,并避免了变量作用域过大的陷阱。
在这里插入图片描述

  • 53
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 20
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lion Long

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值