精华内容
下载资源
问答
  • C++17新特性个人总结

    2020-12-17 06:59:56
    C++17 编译器版本:GCC 7.1、Clang 5.0 __cplusplus:201703L 编译选项:-std=c++17 1 关键字 1.1 constexpr 扩展constexpr使用范围,可用于if语句中,也可用于lambda表达式中。 例子1: #include template ...
  • C++20新特性.pdf

    2020-06-26 22:11:06
    这是C++20新特性英文文档,基本涵盖了C++20的所有新特性。在这里提供给大家一起学习、研究,借此机会推广C++
  • 本文所谓的C++新特性是指C++11及其以后的C++14、C++17增加的关键字和新语法特性。其中C++11是C++98以来最重要的一次变化,而其后的C++14、C++17是在该基础上的完善和补充。 那么,问题来了,如果我不想学习新特性...

    学习SLAM,C++编程是必备技能。不过,大家在学校里学习的书本一般比较老,主要还是C++98那些老一套。

    本文所谓的C++新特性是指C++11及其以后的C++14、C++17增加的新关键字和新语法特性。其中C++11是C++98以来最重要的一次变化,而其后的C++14、C++17是在该基础上的完善和补充。

    那么,问题来了,如果我不想学习新特性,还是像以前那样编程可以吗?

    答案是:可以,不过这就像是别人已经在用铁制刀具切瓜了,你还在石器时代使用石头刀具,你说,效率能一样吗?

    看看下面你心里就有答案了。

    为何要学习新特性?

    1、可以大幅度提高编程效率,切瓜更6

    C++新特性增加了很多非常高效的关键字和语法,比如std::swap,C++11之前的swap执行了三次内存拷贝操作,这种不必要的内存操作会影响效率。而C++11之后的swap引入了右值引用和数据移动的概念,使用内存移管代替了不必要的内存拷贝,大大提高了效率。

    2、省心省力,减少脱发

    举个栗子。C++11之前如果我们要定义并初始化一个新变量,必须得知道其类型并定义,这在很多时候是非常繁琐的,比如迭代器的使用,而C++11之后引入了自动类型推导,一个auto解决一切,不需要关心类型,编译器会帮你自动推导出类型。

    3、能看懂别人的代码,不被新时代抛弃

    由于上述的优点,很多开源代码都是使用C++新特性,工作学习中大家也都使用新特性,如果你根本不了解这些新特性,估计很难看懂别人的代码,时代的战车会轰隆隆把你抛弃。

    下面就列举几个比较典型的、经常使用的新特性,供大家入门。

    常用的C++新特性

    1、更方便的列表初始化

    C++11前只有数组能使用初始化列表。而C++11后大部分类型都可以初始化列表,方便的很。如下所示。
    在这里插入图片描述

    2、省心省力的自动类型推导

    C++11中引入了auto,可以用来进行自动类型推导,不用关心数据类型,编译器会帮你推导好,而且这种方式也不影响编译速度。

    比如迭代器使用了自动类型推导后,感觉清爽多了,如下图所示:
    在这里插入图片描述

    不过,auto使用时也有需要注意的地方:

    比如auto不能代表一个实际的类型声明,auto声明的变量必须马上初始化

    见下图:
    在这里插入图片描述

    更多内容可以网上查询,如果懒得找可以参考:

    https://blog.csdn.net/m_buddy/article/details/72828576

    3、简洁舒服的循环体

    for循环是使用频率非常高的循环方式,在新特性里我们不需要再像以前那样每次都使用自增或者自减的方式来索引了,结合前面介绍的auto,我们可以极大简化循环方式,如下图所示:

    在这里插入图片描述
    而且这种循环支持大部分数据类型,比如数组,容器,字符串,迭代器等等。
    在这里插入图片描述

    4、简洁的lambda表达式

    lambda表达式可以使得编程非常简洁,尤其适用于简单的函数,一般形式如下:

    [函数对象参数](操作符重载函数参数)->返回值类型{函数体}

    下面是几个例子及解释:
    在这里插入图片描述

    5、随心所欲可变长的参数模板

    在Python和MATLAB中可以非常方便的使用可以变长的参数,C++11以后引入了tuple,可以实现类似功能,并且可以传入多个不同类型的数据,如下图所示
    在这里插入图片描述

    6、其他新特性自己学吧

    除了上述介绍的常用的几种新特性外,还有很多新特性,比如:

    decltype:像auto的反函数,可以从一个变量或表达式中得到类型;

    nullptr:解决原来C++中NULL的二义性问题;

    STL容器、智能指针、多线程相关的新特性

    可以在网上查到很多资料,如果懒得找,可以看看下面两个链接。

    https://www.cnblogs.com/feng-sc/p/5710724.html

    http://towriting.com/blog/2013/08/01/what-is-cpp11/

    文末知识星球里也有相关的电子书和视频讲解。

    练习题

    请使用C++新特性改写以下函数。该函数功能:将一组无序的坐标按照“Z”字形排序,并输出。

    本程序学习目标:

    熟悉C++新特性(简化循环、自动类型推导、列表初始化、lambda函数)

    题目:
    在这里插入图片描述

    正确输出结果:
    在这里插入图片描述

    如果想要下载以上代码框架,在公众号菜单栏回复:11,即可。

    欢迎留言讨论,更多学习视频、文档资料、参考答案等扫描下方二维码进入知识星球「从零开始学习SLAM」和其他学习SLAM学的伙伴一起学习交流~

    相关阅读

    从零开始一起学习SLAM | 为什么要学SLAM?

    从零开始一起学习SLAM | 学习SLAM到底需要学什么?

    从零开始一起学习SLAM | SLAM有什么用?

    零基础小白,如何入门计算机视觉?

    原文链接:从零开始一起学习SLAM | C++新特性要不要学?

    展开全文
  • C++14 新特性

    千次阅读 2020-02-27 11:43:04
    二、的语言特性… 1、 泛型的 Lambda 函数… 2、 Lambda 捕获表达式… 3、 函数返回类型推导… 4、 另一种类型推断… 5、 放松的 constexpr 限制… 6、 变量模板… 7、 聚合体成员初始化… 8、 二进制字面值… 9、...

    一、目录

    一、目录…
    二、新的语言特性…
    1、 泛型的 Lambda 函数…
    2、 Lambda 捕获表达式…
    3、 函数返回类型推导…
    4、 另一种类型推断…
    5、 放松的 constexpr 限制…
    6、 变量模板…
    7、 聚合体成员初始化…
    8、 二进制字面值…
    9、 数字分位符…
    三、新的标准库特性…
    1、 共享的互斥体和锁…
    2、 元函数的别名…
    3、 关联容器中的异构查找…
    4、 标准自定义字面值…
    5、 通过类型寻址多元组…
    6、较小的标准库特性…
    四、 已被移除或是不包含在 C++14 标准的特性…
    1、 关于数组的扩展…
    2、 Optional 值…
    3、 Concepts Lite…

    二、新的语言特性

    1 、泛型的 Lambda 函数

    在 C++11 中,lambda 函数的形式参数需要被声明为具体的类型。C++14 放宽了这一要求,允许 lambda 函数的形式参数声明中使用类型说明符 auto。

    auto lambda = [](auto x, auto y) {return x + y;}
    

    泛型 lambda 函数遵循模板参数推导的规则。以上代码的作用与下面的代码相同:

    struct unnamed_lambda
    {
    template<typename T, typename U>
    auto operator()(T x, U y) const {return x + y;}
    };
    
    auto lambda = unnamed_lambda();
    

    2 、Lambda 捕获表达式

    C++11 的 lambda 函数通过值拷贝(by copy)或引用(by reference)捕获(capture)已在外层作用域声明的变量。这意味着 lambda 的值成员不可以是 move-only 的类型。C++14允许被捕获的成员用任意的表达式初始化。这既允许了 capture by value-move,也允许了任意声明 lambda 的成员,而不需要外层作用域有一个具有相应名字的变量。

    这是通过使用一个初始化表达式完成的:

    auto lambda = [value = 1] {return value;}
    

    lambda 函数 lambda 的返回值是 1,说明 value 被初始化为 1。被声明的捕获变量的类型会根据初始化表达式推断,推断方式与用 auto 声明变量相同。

    使用标准函数 std::move 可以使之被用以通过 move 捕获:

    auto ptr = std::make_unique<int>(10); //See below for std::make_unique
    auto lambda = [ptr = std::move(ptr)] {return *ptr;}
    

    声明 ptr = std::move(ptr)使用了两次 ptr。第一次使用声明了一个新的变量,但在捕获部分,这个变量还不在作用域内。所以第二个 ptr 表示之前在 lambda 之外声明的变量。

    3 、函数返回类型推导

    C++11 允许 lambda 函数根据 return 语句的表达式类型推断返回类型C++14 为一般的函数也提供了这个能力。C++14 还拓展了原有的规则,使得函数体并不是{return expression;}形式的函数也可以使用返回类型推导。

    为了启用返回类型推导,函数声明必须将 auto 作为返回类型,但没有 C++11 的后置返回类型说明符:

    auto DeduceReturnType(); //返回类型由编译器推断
    

    如果函数实现中含有多个 return 语句,这些表达式必须可以推断为相同的类型。[9]

    使用返回类型推导的函数可以前向声明,但在定义之前不可以使用。它们的定义在使用它们的翻译单元(translation unit)之中必须是可用的。

    这样的函数中可以存在递归,但递归调用必须在函数定义中的至少一个 return 语句之后:

    auto Correct(int i) 
    {
    	if (i == 1)
    		return i; // 返回类型被推断为 int
    	else
    		return Correct(i-1)+i; // 正确,可以调用
    }
    
    auto Wrong(int i)
    {
    	if(i != 1)
    		return Wrong(i-1)+i; // 不能调用,之前没有 return 语句
    	else
    		return i; // 返回类型被推断为 int
    }
    

    4 、另一种类型推断

    C++11 中有两种推断类型的方式。auto 根据给出的表达式产生具有合适类型的变量。decltype 可以计算给出的表达式的类型。但是,decltype 和 auto 推断类型的方式是不同的。

    特别地,auto 总是推断出非引用类型,就好像使用了 std::remove_reference 一样,而 auto&&总是推断出引用类型。然而 decltype 可以根据表达式的值类别(value category)和表达式的性质推断出引用或非引用类型:

    int i;
    int&& f();
    auto x3a = i; // x3a 的类型是 int
    decltype(i) x3d = i; // x3d 的类型是 int
    auto x4a = (i); // x4a 的类型是 int
    decltype((i)) x4d = (i); // x4d 的类型是 int&
    auto x5a = f(); // x5a 的类型是 int
    decltype(f()) x5d = f(); // x5d 的类型是 int&&
    

    C++14 增加了 decltype(auto)的语法。这允许不必显式指定作为 decltype 参数的表达式,而使用 decltype 对于给定表达式的推断规则。

    decltype(auto)的语法也可以用于返回类型推导,只需用 decltype(auto)代替 auto。

    5 、放松的 constexpr 限制

    C++11 引入了 constexpr 函数的概念,这样的函数可以在编译期执行。它们的返回值可以用于期望常量表达式的场合(如模板的非类型参数)。然而 C++11 要求 constexpr 函数只含有一个将被返回的表达式(也可以还含有 static_assert 声明等其它语句,但允许的语句类型很少)。

    C++14 将放松这些限制。声明为 constexpr 的函数将可以含有以下内容:
     任何声明,除了:static 或 thread_local 变量、没有初始化的变量声明。
     条件分支语句 if 和 switch。goto 是不允许的。
     所有的循环语句,包括基于范围的 for 循环。
     表达式可以改变一个对象的值,只需该对象的生命期在常量表达式函数内开始。包括对有 constexpr 声明的任何非 const 非静态成员函数的调用。

    调用非 constexpr 函数仍然是受限的。所以如果使用基于范围的 for 循环,begin 和 end的重载形式必须自身被声明为 constexpr。值得注意的是,std::initializer_list 在本地和全局都具有 constexpr 声明的 begin/end 函数。

    此外,C++11 指出,所有被声明为 constexpr 的非静态成员函数也隐含声明为 const(即函数不能修改*this 的值)。这点已经被删除,非静态成员函数可以为非 const。然而,只有当对象的生命期在常量表达式求值中开始,非const的constexpr成员函数才可以修改类成员。

    6 、变量模板

    在 C++之前的版本中,模板可以是函数模板或类模板(C++11 引入了类型别名模板)。C++14 现在也可以创建变量模板。包括特化在内,通常的模板的规则都适用于变量模板的声明和定义。

    7 、聚合体成员初始化

    C++11 新增 member initializer,这是一个表达式,被应用到类作用域的成员上,如果构造函数没有初始化这个成员。聚合体的定义被改为明确排除任何含有 member initializer 的类,因此,他们不允许使用聚合初始化。C++14 将放松这一限制,这种类型也允许聚合初始化。如果花括号初始化列表不提供该参数的值,member initializer 会初始化它。

    8 、二进制字面值

    C++14 的数字可以用二进制形式指定。其格式使用前缀 0b 或 0B。这样的语法也被 Java、Python、Perl 和 D 语言使用。

    9 、数字分位符

    C++14 引入单引号(’)作为数字分位符号,使得数值型的字面量可以具有更好的可读性。Ada、D 语言、Java、Perl、Ruby 等程序设计语言使用下划线(_)作为数字分位符号,C++之所以不和它们保持一致,是因为下划线已被用在用户自定义的字面量的语法中。

    auto integer_literal = 100'0000;
    auto floating_point_literal = 1.797'693'134'862'315'7E+308;
    auto binary_literal = 0b0100'1100'0110;
    auto silly_example = 1'0'0'000'00;
    

    三、新的标准库特性

    1 、共享的互斥体和锁

    C++14 增加了一类共享的互斥体和相应的共享锁。起初选择的名字是 std::shared_mutex,但由于后来增加了与 std::timed_mutex 相似的特性,std::shared_timed_mutex 成为了更适合的名字。

    2 、元函数的别名

    C++11 定义了一组元函数,用于查询一个给定类型是否具有某种特征,或者转换给定类型的某种特征,从而得到另一个类型。后一种元函数通过成员类型 type 来返回转换后的类型,当它们用在模板中时,必须使用 typename 关键字,这会增加代码的长度。

    template <class T>
    type_object<
    typename std::remove_cv<
    typename std::remove_reference<T>::type
    >::type
    >get_type_object(T&);
    

    利用类型别名模板,C++14 提供了更便捷的写法。其命名规则是:如果标准库的某个类模板(假设为 std::some_class)只含有唯一的成员,即成员类型 type,那么标准库提供std::some_class_t作为 typename std::some_class::type 的别名。在 C++14,拥有类型别名的元函数包括:remove_const、remove_volatile、remove_cv、add_const、add_volatile、add_cv、remove_reference、add_lvalue_reference、add_rvalue_reference、make_signed、make_unsigned、remove_extent、remove_all_extents、remove_pointer、add_pointer、aligned_storage、aligned_union、decay、enable_if、conditional、common_type、underlying_type、result_of、tuple_element。

    template <class T>
    type_object<std::remove_cv_t<std::remove_reference_t<T>>>
    get_type_object(T&);
    

    3 、关联容器中的异构查找

    C++标准库定义了四个关联容器类。set 和 multiset 允许用户根据一个值在容器中查找对应的的同类型的值。map 和 multimap 容器允许用户指定键(key)和值(value)的类型,根据键进行查找并返回对应的值。然而,查找只能接受指定类型的参数,在 map 和 multimap中是键的类型,而在 set 和 multiset 容器中就是值本身的类型。C++14 允许通过其他类型进行查找,只需要这个类型和实际的键类型之间可以进行比较操作。[17]这允许 std::setstd::string使用 const char*,或任何可以通过 operator< 与 std::string比较的类型作为查找的参数。为保证向后兼容性,这种异构查找只在提供给关联容器的比较器允许的情况下有效。标
    准库的泛型比较器,如 std::less<>与 std::greater<>允许异构查找。

    4 、标准自定义字面值

    C++11 增加了自定义字面量(user-defined literals)的特性,使用户能够定义新的字面量后缀,但标准库并没有对这一特性加以利用。C++14 标准库定义了以下字面量后缀:
     “s”,用于创建各种 std::basic_string 类型。
     “h”、“min”、“s”、“ms”、“us”、“ns”,用于创建相应的 std::chrono::duration 时间间隔。

    using namespace std::literals;
    std::string str = "hello world"s;
    std::chrono::seconds dur = 60s;
    

     两个"s"互不干扰,因为表示字符串的只能对字符串字面量操作,而表示秒的只针对数
    字。

    5 、通过类型寻址多元组

    C++11 引入的 std::tuple 类型允许不同类型的值的聚合体用编译期整型常数索引。C++14还允许使用类型代替常数索引,从多元组中获取对象。若多元组含有多于一个这个类型的对象,将会产生一个编译错误:

    tuple<string, string, int> t("foo", "bar", 7);
    int i = get<int>(t); // i == 7
    int j = get<2>(t); // Same as before: j == 7
    string s = get<string>(t); //Compiler error due to ambiguity
    

    6 、较小的标准库特性

    std::make_unique 可以像 std::make_shared 一样使用,用于产生 std::unique_ptr 对象。
    std::is_final,用于识别一个 class 类型是否禁止被继承。
    std::integral_constant 增加了一个返回常量值的 operator()。
    全局 std::begin/std::end 函数之外,增加了 std::cbegin/std::cend 函数,它们总是返回常量迭代器(constant iterators)。

    四、已被移除或是不包含在 C++14 标准的特性

    1 、关于数组的扩展

    在 C++11 和之前的标准中,在堆栈上分配的数组被限制为拥有一个固定的、编译期确定的长度。这一扩展允许在堆栈上分配的一个数组的最后一维具有运行期确定的长度。运行期确定长度的数组不可以作为对象的一部分,也不可以具有全局存储期,他们只能被声明为局部变量。运行期确定长度的数组也可以使用 C++11 的基于范围的 for 循环。

    同时还将添加 std::dynarray 类型,它拥有与 std::vector 和 std::array 相似的接口。代表一个固定长度的数组,其大小在运行期构造对象时确定。std::dynarray 类被明显地设计为当它被放置在栈上时(直接放置在栈上,或作为另一个栈对象的成员),可以使用栈内存而是堆内存。

    2 、Optional 值

    类似于 C#中的可空类型,optional 类型可能含有或不含有一个值。这一类型基于 Boost的 boost::optional 类,而添加了 C++11 和 C++14 中的新特性,诸如移动和 in-place 构造。它不允许用在引用类型上。这个类被专门的设计为一个 literal type(如果模板参数本身是一个literal type),因此,它在必要的情况下含有 constexpr 构造函数。

    3 、Concepts Lite

    被 C++11 拒绝后,Concepts 受到彻底的修改。Concepts Lite 是 Concepts 的一个部分,仅包含类型约束,而不含 concept_map 和 axiom。它将在一个单独的 Technical Specification中发展,并有可能加入 C++17。

    展开全文
  • c++11新特性全面系统介绍,使用工具书
  • 注意:不是原版课件,是视频截图拼接成的pdf,不影响看。暂时缺少第35和36讲的课件。
  • C++20新特性—概述

    千次阅读 2020-12-07 10:31:25
    其实,一门编程语言的变化大致上是应该分两个层面,一个是语言层面,即增加了语言新特性;二是库层面,其实就是各种“语法糖”等,它们是在语言层面的基础上扩展、实现出来的,使用起来更“接地气”。 cppreference...

    ISO / IEC 14882:2020(即C++20标准)标准草案在2020年9月获得了全票通过,年底能正式发布。对C++20,多数的评论是它具有可与C++11相比较的重大变化。

    下图是Rainer Grimm博文中给出的C++20的主要变化。

    其实,一门编程语言的变化大致上是应该分两个层面,一个是语言层面,即增加了语言新特性;二是库层面,其实就是各种“语法糖”等,它们是在语言层面的基础上扩展、实现出来的,使用起来更“接地气”。

    cppreference 网站也给出了C++20的主要变化(https://en.cppreference.com/w/cpp/20),它就分了两个部分:语言新特性和运行库新特性,其中运行库新特性又分为新增库和新增类/函数,如下图所示。

    从目前的资料来看,C++20的变化(以笔者的判断,按“改变度”从大到小)主要体现在:

    • module:module的引入,几乎是革命性的,它同时增加了import,export两个关键字,使C++可以摆脱C中“重复定义”,函数定义需要写两遍等历史包袱,变得“现代”起来。
    • concept(包括require):大量模板的使用,真的很有必要对模板参数的类型做一定的约束,concept就是为此目的,其实,C#早就引入了where,现在C++补上了这一部分,对模板参数的约束比以前表达得简洁多了。

    --上面可谓是“大”变化。

    • Coroutines:“并行”执行的一种方法,作为多线程的一种替代,特别适合“慢速”IO和“高速”计算并行的场合。Coroutines的概念也是早就有了,一些语言相继引入,之前boost库中实现了Coroutine的方法,C++11中实现了promise,现在C++引入了co_await、co_yeild、co_return这样的关键字,终于在语言层面实现了Coroutine。
    •  Range,span:C++库中大量的集合操作,如果有表达“范围“的操作,一般采用 “映射函数”+新建集合的方法,现在有了range,span,view等概念,并且有一个range adaptor(|),此时表达就简洁多了。
    •  3-way comparasion:C++定义了一个新的有趣的比较符:<=>,它形似飞船,因而也称作“spaceship operator”。集合操作中很常用的操作是“比大小”,<=>提供了一种更简洁的表达方式。
    • consteval, constinit:这两个关键字与constexpr有点类似,都是为了提高运行时速度而在编译时做处理。

    --上面可谓是“小”变化。

    • Feature testing:定义了几个新的attributes,如likely, no_unique_address等,以及一些宏,可用于版本、功能的检测。
    • 新的线程支持:增加了jthread线程类和semaphore同步类等新的特性,与jthread相适应,增加线程外通知机制,总体感觉有一定用途,但不一定是必须的或自己也可以实现简化版。
    • source_location:新增加< source_location>,它被划分为utility,主要为了信息跟踪(调试),而代替__FILE__,__LINE__等。
    •  text format:新的格式化输出表达方式,感觉是C#输出方式的“模拟”。
    • 时间库增强:主要是增加了时区(time zone)和日历(calendar)方面的计算、判断等。
    •  计算(numeric)增强:增加了一些计算类(宏),endian判断支持以及按bit转换等功能
    • Synchronized output:增加了basic_syncbuf、basic_osyncstream模板类以及特化的类,它们被定义在<syncstream>中,在不同线程中,向它们的输出不会再相互干扰了。

    --上面可谓是增强。

    除上述外,还可以列出一些。在笔者看来,C++20确实给语言带来比较大的变化,特别是Modules的引入,有让人感叹C++与C “渐行渐远”,而与C#,Java逐渐趋同的意味。
    后面将分别介绍这些C++20引入的新特性。

     

    展开全文
  • C++20新特性个人总结

    万次阅读 多人点赞 2020-06-26 21:54:40
    C++20 编译器版本:GCC 10 __cplusplus:202003L 编译选项:-std=c++2a 1 关键字 1.1 concepts concepts乃重头戏之一,用于模板库的开发。功能类似于C#的泛型约束,但是比C#泛型约束更为强大。 concepts...

    目录

    1  关键字

    1.1  concept

    1.2  requires

    1.3  typename

    1.4  explicit

    1.5  constexpr

    1.6  char8_t

    1.7  consteval

    1.8  co_await、co_yield、co_return

    1.9  constinit

    2  语法

    2.1  位域变量的默认成员初始化

    2.2  修改const限定的成员指针

    2.3  允许lambda表达值按值捕获this

    2.4  指定初始化

    2.5  lambda表达式支持模板

    2.6  从构造函数推导出模板参数类型

    2.7  基于范围的for循环初始化

    2.8  简化lambda的隐式捕获

    2.9  ADL与不可见的模板函数

    2.10  operator<=>

    2.11  基于范围的for循环初始化

    2.12  默认可构造可分配的无状态lambdas

    2.13  专门的访问检查

    2.14  constexpr函数的实例化

    2.15  允许lambda在初始化捕获时进行包扩展

    2.16  放宽结构化绑定,新增自定义查找规则

    2.17  放宽基于范围的for循环,新增自定义范围方法

    2.18  类类型的非类型模板参数

    关于类类型的非类型模板参数的优化

    类类型的非类型模板参数的条件(满足任意一个):

    2.19  禁止使用用户自己声明的构造函数来进行聚合初始化

    旧版的几个问题

    解决方案

    2.20  嵌套内联命名空间

    2.21  约束声明的另一种办法

    2.22  允许在常量表达式中使用dynamic_cast多态typeid

    2.23  允许用圆括弧的值进行聚合初始化

    2.24  new表达式的数组元素个数的推导

    2.25  unicode字符串字面量

    2.26  允许转换成未知边界的数组

    2.27  聚合初始化推导类模板参数

    2.28  隐式地将返回的本地变量转换为右值引用

    2.29  允许default修饰运算符按值比较

    2.30  非类型模板参数等效的条件

    3  宏

    4  属性

    4.1  likely和unlikely

    4.2  no_unique_address

    4.3  nodiscard

    5  弃用

    5.1  lambda弃用使用[=]来隐式捕获this

    5.2  比较运算符的改进

    5.3  弃用下标表达式中的逗号操作符

    后记


     

    C++20

    编译器版本:GCC 11

    __cplusplus:202002L

    编译选项:-std=c++2a

     

    1  关键字

    1.1  concept

    concept乃重头戏之一,用于模板库的开发。功能类似于C#的泛型约束,但是比C#泛型约束更为强大。

    concept用于声明具有特定约束条件的模板类型。

    例子:数值类型约束

    #include <type_traits>
    
    // 声明一个数值类型的concept
    template<typename T>
    concept number = std::is_arithmetic<T>::value; // 对模板类型T添加std::is_arithmetic<T>::value为true的约束,并对具有约束的新的类型声明number
    
    // 使用具有约束的类型,调用该函数时,T类型必须符合std::is_arithmetic<T>::value等于true,否则编译报错
    template<number T>
    void func(T t)
    { }
    
    // 调用
    func<int>(10); // 正确,std::is_arithmetic<int>::value为true
    func<double>(20.0); // 正确,std::is_arithmetic<double>::value为true
    
    struct A
    { };
    func<A>(A()); // 错误,std::is_arithmetic<A>::value为false

     

    1.2  requires

    单纯一个concept还不够强大,真正让concept起飞的是这个requires,concept结合requires之后,对模板类型参数的约束可以细致到类型成员变量、类型成员函数甚至其返回值等等。

    例子:约束类型具有指定名称的成员变量、成员函数

    #include <type_traits>
    
    template<typename T>
    concept can_run = requires(T t)
    {
        std::is_class<T>::value; // T是一个类型
        t(); // T类型有重载括号运算符,且是无参的
        t.run(); // T类型具有run()成员函数
        std::is_same<decltype(t.run()), int>::value; // T类型的run()函数的返回值为int类型
    }
    
    // concepts类型使用
    template<can_run T>
    int func(T t)
    {
        t();
        return t.run(); // run()函数的返回值已被限定为int类型,所以此处可直接返回
    }
    
    func<int>(10); // 错误,这不是一个class或struct
    
    struct A
    {
        void run() { }
    }
    func<A>(A()); // 编译错误,没有重载括号运算符
    
    struct B
    {
        void operator()() { }
    }
    func<B>(B()); // 编译错误,没有run()函数
    
    struct C
    {
        void operator()() { }
        void run() { }
    }
    func<C>(C()); // 编译错误,run()函数返回值不是int类型
    
    struct D
    {
        int operator()() { }
        int run() { return 0; } 
    }
    func<D>(D()); // 正确,编译通过

    1.3  typename

    typename主要两种用法:①模板类型声明②声明一个名称是类型名。此前为了解决冲突问题,功能②被大量地使用,新版本为了提高可读性,加强了编译的推导能力,简化typename在功能②的使用。

    在一些地方,例如在某指定的上下文中只能推导为类型的地方,可不加typename。

    例子:

    // 函数的返回值,在全局范围内只可能是一种类型,所以可不加typename
    template<class T> T::R f(); // OK, return type of a function declaration at global scope
    
    // 作为函数的参数,
    template<class T> void f(T::R); // Ill-formed (no diagnostic required), attempt to declare a void variable template
    
    template<typename T>
    struct PtrTraits
    {
        typedef T* Ptr;
    };
    
    template<class T> 
    struct S 
    {
        using Ptr = PtrTraits<T>::Ptr; // OK, in a defining-type-id
    
        T::R f(T::P p) 
        { // OK, class scope
            return static_cast<T::R>(p); // OK, type-id of a static_cast
        }
    
        auto g() -> S<T*>::Ptr;// OK, trailing-return-type
    };
    
    template<typename T> void f() 
    {
        void (*pf)(T::X); // Variable pf of type void* initialized with T::X
        void g(T::X); // Error: T::X at block scope does not denote a type
        // (attempt to declare a void variable)
    }
    

    1.4  explicit

    新增bool参数,表示explicit本身的作用是否启用

    例子:

    struct A
    {
        explicit(false)
        A(int) { }
    };
    struct B
    {
        explicit(true)
        B(int) { }
    };
    
    A a = 10; // 正确
    B b = 10; // 错误:将int类型转换为B类型

    1.5  constexpr

    ①扩展适用范围,新增对虚函数的支持,用法与普通函数一致,不再赘述。

    ②禁止constexpr函数内使用try-catch语句块。不再赘述。

    1.6  char8_t

    为utf-8字符编码专门打造,以后就由char8_t类型接收utf-8字面量,而不再由char接收。

    编译器未完全实现,待续。

    1.7  consteval

    编译器未实现,待续。

    1.8  co_await、co_yield、co_return

    协程三件套:co_await、co_yield、co_return

    由于编译器未支持,详解待续。

    1.9  constinit

    用于强制常量进行初始化,不可动态初始化。

    变量条件:静态 或 线程存储持续时间。thread_local修饰的变量可不进行初始化

    例子:

    const char * get_str1()
    {
        return "111111";
    }
    constexpr const char * get_str2()
    {
        return "222222";
    }
    
    const char *hahah = " hhahahaa ";
    
    constinit const char *str1 = get_str2(); // 编译正确
    constinit const char *str2 = get_str1(); // 编译错误,用非constexpr函数对constinit变量进行初始化
    constinit const char *str3 = hahah; // 编译错误,用非常量表达式对constinit变量进行初始化
    
    int main()
    {
        static constinit const char *str4 = get_str2(); // 编译正确
        constinit const char *str5 = get_str2();// 编译错误,必须是静态 或 线程存储持续时间的变量
        constinit thread_local const char *str6; // 编译正确
        return 0;
    }

     

    2  语法

    2.1  位域变量的默认成员初始化

    位域变量在声明时可进行初始化。

    位域变量的声明语法格式:

    • 标识符 变量名 : 位数
    • 标识符 变量名 : 常量表达式、大括号

    例子:

    int a;
    const int b = 1;
    
    struct S
    {
        int x1 : 8 = 42;   // 正确,x1为8位的变量,并且初始化为42,“=42”为常量表达式
        int x2 : 6 {42};   // 正确,x2为6位的变量,并且初始化为42
        int x3 : true ? 10 : a = 20; // 正确,x3为10位变量,不进行初始化,赋值号优先于三目运算符
        int x4 : true ? 10 : b = 20; // 错误,b为const变量,不可赋值
        int x5 : (true ? 10 : b) = 20; // 正确,x5为10位的变量,并且初始化为20
        int x6 : false ? 10 : a = 20; // 错误,a = 10不是常量表达式
    };

     

    2.2  修改const限定的成员指针

    在一个右值的 .* 表达式中,如果表达式的第二个参数是指向以&修饰的成员函数的指针,那么这个程序就是不规范的,除非限定符是const

    例子:

    struct S { void foo() const& { } };
    
    void f()
    {
        S{}.foo(); // 正确,没问题
        (S{}.*&S::foo)(); // C++20起支持该语法
    }

     

    2.3  允许lambda表达值按值捕获this

    例子:

    struct S
    {
        int value;
        void print()
        {
            auto f = [=, this]() {
                this->value++;
            };
        }
    }

     

    2.4  指定初始化

    在构造对象时,可以指定成员进行初始化,但是初始化的顺序必须与成员的内存顺序一致。

    例子:

    struct A { int x, y; };
    struct B { int y, x; };
    
    
    void f(A a, int); // #1
    void f(B b, …); // #2
    void g(A a); // #3
    void g(B b); // #4
    
    void h()
    {
        f({.x = 1, .y = 2}, 0); // 正确,调用#1
        f({.y = 1, .x = 2}, 0); // 错误,调用#1,初始化顺序不匹配
        f({.y = 1, .x = 2}, 1, 2, 3); // 正确,调用#2
        g({.x = 1, .y = 2}); // 错误,无法确定调用#3还是#4
    }
    
    

     

    2.5  lambda表达式支持模板

    从新版开始,lambda表达式支持模板编程,且支持自动推导。(官方的说明是:支持未鉴定的上下文)

    例子1:

    int a;
    
    auto f = [&a]<typename T>(const T &m) {
        a += m;
    };
    
    f(10);

    例子2:

    template<typename T>
    int func(int t) 
    {
        return t * t;
    }
    
    int f()
    {
        return func<decltype([] {})>(20);
    }

    例子3:

    using A = decltype([] {});
    void func(A *) { }
    
    func(nullptr);
    
    
    template<typename T>
    using B = decltype([] {});
    
    void f1(B<int> *) { }
    
    template<typename T>
    void f2(B<T> *) { }
    
    f1(nullptr);
    f2<int>(nullptr); 

     

    2.6  从构造函数推导出模板参数类型

    声明变量时进行初始化,如果能从构造函数中推导出变量类型,则该变量的类型可以不用指定模板参数。

    例子:

    vector v{vector{1, 2}}; // 正确,v 推导为vector<vector<int>>类型
    tuple t{tuple{1, 2}}; //正确,t 推导为tuple<int, int>类型

     

    2.7  基于范围的for循环初始化

    直接上例子:

    void f(const vector<int> &v)
    {
        for(int c = 0; int a : v)
        {
            cout << a << “ ”;
            if(++c % 5 == 0)
            {
                cout << endl;
            }
        }
    }

     

    2.8  简化lambda的隐式捕获

    本人水平有限,暂时不能展示。

     

    2.9  ADL与不可见的模板函数

    ADL是C++本来就有的机制,用于自动推断调用的函数的位置,从而简化代码的编写。而新特性扩展了ADL机制,可以用于模板函数的推断。

    例子:

    int h;
    void g();
    
    namespace N
    {
        struct A {};
        template<typename T> int f(T);
        template<typename T> int g(T);
        template<typename T> int h(T);
    }
    
    int x = f<N::A>(N::A()); // 正确,调用N::f
    int y = g<N::A>(N::A()); // 正确,调用N::g
    int z = h<N::A>(N::A()); // 错误,h是变量,不是模板

     

    2.10  operator<=>

    因为篇幅过长就不再在这里详细赘述了,感兴趣的可以自行查看http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0515r3.pdf。因此,我在这里只简单说一下。

    来了解一下不同类型的比较策略(专有名词就不翻译了),并且可进行向下进行相对应的隐式转换:

    策略数值型结果非数值型结果
    -101
    strong_orderinglessequalgreaterunordered
    weak_orderinglessequivalentgreater
    partial_orderinglessequivalentgreater
    strong_equalityunequalequalunequal
    weak_equalitynonequivalentequivalentnonequivalent

    至于什么时候用到哪一种策略,这里有一位博主翻译好了的https://blog.csdn.net/qq_31359295/article/details/78799020,这里不再讲解(懒)。

    2.11  基于范围的for循环初始化

    新增的for循环语法格式:

    for([init-statement;] for-range-declaration : for-range-initializer) ...

    例子:

        int a[] = {1, 2, 3, 4};
        for(int b = 0; int i : a) 
        {
            ...
        }

     

    2.12  默认可构造可分配的无状态lambdas

    简单点说,就是可以获取lambda或函数对象的类型,并且还可以创建对象。

    举个例子感受一下:

    #include <iostream>
    #include <map>
    
    auto greater = [](auto x, auto y) { return x > y; };
    std::map<std::string, int, decltype(greater)> map;
    
    static void f() 
    {}
    
    int main() 
    {
        decltype(f) ff;
        ff();
    
        decltype(greater) d;
        d(10, 20);
        return 0;
    }

    2.13  专门的访问检查

    我能力有限,不能准确理解文档的意思。这个特性在GCC、MSVC编译器中早已实现,但在其他的编译器以前的版本中并未实现。

    我的理解是,在模板类内,可以忽略访问权限而访问到其他类内的嵌套类。

    例子:

    class A
    {
        struct impl1
        { int value; };
    
        template<typename T>
        class impl2
        { T value; };
    
        class impl3
        { int value; };
    };
    
    struct B
    {
        A::impl1 t; // error: 'struct A::impl1' is private within this context
    };
    
    template<typename T>
    struct trait
    {
        A::impl1 t;     // ok
        A::impl2<T> t2; // ok
    
        void func()
        {
            A::impl1 tmp;  // ok
            tmp.value = 10;// ok
            t2.value = 20; // ok
    
            A::impl3 t3;   // ok
            t3.value = 30; // ok
        }
    };
    
    int main() 
    {
        trait<int> a;
        a.t.value = 10; // ok
        a.t2.value = 20; // error: 'int A::impl2<int>::value' is private within this context
        return 0;
    }
    

    2.14  constexpr函数的实例化

    当仅仅获取constexpr函数的返回值类型时,不对函数进行实例化,即仅推导返回值类型,而不对函数进行调用。

    template<typename T> 
    constexpr int f() 
    { return T::value; }
    
    // 此处仅仅推导f<T>()的返回值类型
    template<bool B, typename T> 
    void g(decltype(B ? f<T>() : 0)) { }
    
    template<bool B, typename T> void g(...) { }
    
    // 因为需要获取int类型的数据,所以需要执行f<T>()函数
    template<bool B, typename T> void h(decltype(int{B ? f<T>() : 0})) { }
    
    template<bool B, typename T> void h(...) { }
    
    void x() 
    {
        g<false, int>(0); // OK, B ? f<T>() : 0 is not potentially constant evaluated
        h<false, int>(0); // error, instantiates f<int> even though B evaluates to false and
                          // list-initialization of int from int cannot be narrowing
    }

    2.15  允许lambda在初始化捕获时进行包扩展

    扩展了包扩展的应用范围

    例子:

    #include <functional>
    
    template<class F, class... Args>
    auto invoke1(F f, Args... args) 
    {
        // 这种写法的效果跟[=]一致
        return [f, args...]() -> decltype(auto) 
        {
            return std::invoke(f, args...);
        };
    }
    
    template<class F, class... Args>
    auto invoke2(F f, Args... args) 
    {
        // 注:三个点号写在参数前面
        return [f=std::move(f), ...args=std::move(args)]() -> decltype(auto) 
        {
            return std::invoke(f, args...);
        };
    }
    
    template<class F, class... Args>
    auto invoke3(F f, Args... args) 
    {
        // 在初始化捕获中构造元组
        return [f=std::move(f), tup=std::make_tuple(std::move(args)...)]() -> decltype(auto) 
        {
            return std::apply(f, tup);
        };
    }
    

    2.16  放宽结构化绑定,新增自定义查找规则

    这个特性比较地牛逼了,以前的结构化绑定的限制比较多,现在放宽了限制,并且可以自定义绑定的第几个是哪个类型,而且可以指定解绑的个数。

    自定义的条件:

    ①在类外实现get<int>(Type)函数、或在类内实现Type::get<int>()成员函数;

    ②在std命名空间内特化tuple_size和tuple_element结构体;

    ③get<int>()的返回路径数量必须与tuple_size指定的数值相等,tuple_element特化的索引数量(且必须从0开始)必须与tuple_size指定的数值相等;

    ④get<int N>()函数中N的值对应的返回类型必须与tuple_element对应索引指定的类型相同。

    例子1:

    #include <string>
    #include <tuple>
    
    struct A
    {
        int a;
        int b;
    };
    
    struct X : private A
    {
        std::string value1;
        std::string value2;
    };
    
    // 第一种方式,类外实现get<>()
    template<int N> 
    auto& get(X &x) 
    {
        if constexpr (N == 0) 
            return x.value2;
    }
    
    namespace std 
    {
        // 指定结构化绑定数量为1个
        template<> 
        class tuple_size<X>
         : public std::integral_constant<int, 1> 
        {};
    
        // 指定结构化绑定的第一种类型为string
        template<> 
        class tuple_element<0, X> 
        {
        public: 
            using type = std::string;
        };
    }
    
    int main()
    {
        X x;
        auto& [y] = x;// y的类型为string
    
        auto& [y1, y2] = x; // error: 2 names provided for structured binding, while 'X' decomposes into 1 element
    
        return 0;
    }

    例子2:

    #include <string>
    #include <tuple> // 必须包含tuple库
    
    struct A
    {
        int a;
        int b;
    };
    
    struct X : protected A
    {
        std::string value1;
        std::string value2;
    
        // 第二种方式,在类内实现get<>
        template<int N> 
        auto& get() 
        {
            if constexpr (N == 0) 
                return value1;
            else if constexpr (N == 1)
                return a;
        }
    
    };
    
    namespace std 
    {
        // 指定X类型结构化绑定的个数为2个
        template<> 
        class tuple_size<X>
         : public std::integral_constant<int, 2> 
        {};
    
        // 指定第一种类型为string类型
        template<> 
        class tuple_element<0, X> 
        {
        public: 
            using type = std::string;
        };
    
        // 指定第二种类型为int类型
        template<> 
        class tuple_element<1, X> 
        {
        public: 
            using type = int;
        };
    
    }
    
    int main()
    {
        X x;
        auto& [y1, y2] = x; // y1为string类型,y2为int类型
    
        return 0;
    }

    2.17  放宽基于范围的for循环,新增自定义范围方法

    以前的版本自定义类的for循环,需要实现begin()和end()的成员函数;新版本开始,可以不实现成员函数,而在类体外实现begin()和end(),具体看以下例子

    例子:

    #include <iostream>
    
    struct X
    {
        int a = 1;
        int b = 2;
        int c = 3;
        int d = 4;
        int e = 5;
    };
    
    int* begin(X& x)
    {
        return reinterpret_cast<int*>(&x);
    }
    
    int* end(X& x)
    {
        return reinterpret_cast<int*>(&x) + sizeof(x) / sizeof(int);
    }
    
    int main()
    {
        X x;
        for (int i : x) 
        {
            std::cout << i << std::endl;
        }
        
        std::cout << "finish" << std::endl;
        return 0;
    }

    2.18  类类型的非类型模板参数

    比较拗口,放松了非类型模板参数的限制,可以用类类型作为模板的参数,但是条件是所需要的运算需要在编译期完成。

    如下例:

    #include <iostream>
    
    struct A
    {
        int value;  
        
        // 这里的constexpr是必须的
        constexpr bool operator==(const A &v) const
        { return value == v.value; }
    };
    
    template<A a, A b>
    struct Equal
    {
        static constexpr bool value = a == b;// 需要在编译期调用operator==
    };
    
    int main()
    {
        static constexpr A a{10}, b{20}; // 作为模板的传入参数,也必须是常量
        std::cout << std::boolalpha << Equal<a, b>::value << std::endl; // 输出false
        std::cout << std::boolalpha << Equal<a, a>::value << std::endl; // 输出true
        return 0;
    }

    关于类类型的非类型模板参数的优化

    ①operator==的缺口

    直接看例子,文字不好写

    #include <iostream>
    
    template<auto v>
    int Value;
    
    struct A
    {
        int value;
    };
    
    int main()
    {
        static constexpr A a{10}, b{20}, c{10};
    
        // 对于Value<a>和Value<b>,只要 (a<=>b) == 0,则&Value<a> == &Value<b>结果就true.
        // 关于 <=> 运算符可以往上面看
    
        std::cout << std::boolalpha << (&Value<a> == &Value<b>) << std::endl; // 输出false
        std::cout << std::boolalpha << (&Value<a> == &Value<c>) << std::endl; // 输出true
        return 0;
    }

    ②模板参数的成员函数调用

    因为模板参数是处于编译期计算的,因此,作为调用用于自定义类型的模板参数的成员函数时,这些成员必须是constexpr修饰的。

    ③类模板参数的相互推导

    例子:

    #include <string>
    
    template<typename _Tp, std::size_t N>
    struct MyArray
    {
        constexpr MyArray(const _Tp (&foo)[N + 1])
        { std::copy_n(foo, N + 1, m_data); }
        
        auto operator<=>(const MyArray &, const MyArray &) = default;
        
        _Tp m_data[N];
    };
    
    template<typename _Tp, std::size_t N>
    MyArray(const _Tp (&str)[N] -> MyArray<_Tp, N - 1>;
    
    template<std::size_t N>
    using CharArray = MyArray<char, N>;
    
    // 在此例子中,用"hello"字符串去实例化A模板时,需要显式的提供size,这导致比较大的不便
    template <std::size_t N, CharArray<N> Str>
    struct A {};
    
    using hello_A = A<5, "hello">;
    
    // 既然这是编译期常量,那在编译期是可以计算出来的,因此C++20做了优化
    template <CharArray Str>
    struct B {};
    
    using hello_B = B<"hello">;

    ④用户自定义字面量

    引用上一个例子

    template <CharArray Str>
    auto operator"" _udl();
    
    "hello"_udl; // 等价于operator""_udl<"hello">()

    类类型的非类型模板参数的条件(满足任意一个):

    ①字面量

    ②是一个lvalue

    ③包含占位符的类型

    ④派生类类型的一个占位符

    ⑤拥有强结构可比较性,没有mutable或者volatile修饰的子对象,拥有声明为public且指定为default的operator<=>

    关于强结构可比较性的定义:

    对于任意一种类型T,const T的一个glvalue对象x,x<=>x是类型std::strong_ordering或者std::strong_equality的有效表达式,它既不调用三向比较操作符,也不调用结构比较运算符。

    2.19  禁止使用用户自己声明的构造函数来进行聚合初始化

    旧版的几个问题

    ①delete了构造函数,却依然可以实例化

    struct X 
    {
        X() = delete;
    };
    
    int main()
    {
        X x1; // 错误,无参构造函数为delete
        X x2{}; // 编译通过了(问题一,实际上应该编译不通过才对)
        return 0;
    }

    ②双重聚合初始化

    struct X 
    {
        int i{4};
        X() = default;
    };
    
    int main()
    {
        X x1(3); // 错误,没有带int类型的构造函数
        X x2{3}; // 编译通过,(问题二,非静态数据成员的双重聚合初始化)
        return 0;
    }

    ③类外指定构造函数default

    struct X 
    {
        int i;
        X() = default;
    };
    struct Y 
    {
        int i;
        Y();
    };
    
    Y::Y() = default;
    
    int main()
    {
        X x{4}; // 正常,编译通过
        Y y{4}; // 编译不通过(问题三,Y结构被判定为非聚合结构)
        return 0;
    }

    解决方案

    简化并统一初始化语义

    如果用户显式声明了非移动和拷贝构造函数的其他构造函数,则类的对象必须通过其中一个构造函数进行初始化。

    上面三个问题的修正结果:

    struct X 
    {
        X() = delete;
    };
    
    int main()
    {
        X x1; // 编译错误,无参构造函数为delete
        X x2{}; // 编译错误,无参构造函数为delete
        return 0;
    }
    struct X 
    {
        int i{4};
        X() = default;
    };
    
    int main()
    {
        X x1(3); // 错误,没有X::X(int)构造函数
        X x2{3}; // 错误,没有X::X({...})构造函数
        return 0;
    }
    #include <initializer_list>
    //--------------------//
    struct X 
    {
        int i;
        X() = default;
    };
    struct Y 
    {
        int i;
        Y();
    };
    Y::Y() = default;
    
    //--------------------//
    struct A
    {
        int i;
        A(int);
    };
    struct B
    {
        int i;
        B(int);
    };
    B::B(int){};
    
    struct C
    {
        int i;
        C() = default;
        C(std::initializer_list<int> list);
    };
    
    int main()
    {
        X x{4}; // 编译错误,没有X::X({...})构造函数
        Y y{4}; // 编译错误,没有X::X({...})构造函数
    
        A a{5}; // 编译通过
        B b{5}; // 编译通过
        C c{6}; // 编译通过
        return 0;
    }

    2.20  嵌套内联命名空间

    简化内联命名空间的嵌套语法

    旧例子:

    #include <iostream>
    
    namespace A
    {
        inline namespace B
        {
            void func()
            {
                std::cout << "B::func()" << std::endl;
            }
        } // namespace B
    } // namespace A
    
    int main()
    {
        A::func(); // 输出 B::func()
        return 0;
    }

    新特性例子:

    #include <iostream>
    
    namespace A
    {
        namespace B
        {
            void func()
            {
                std::cout << "B::func()" << std::endl;
            }
        } // namespace B
    } // namespace A
    
    namespace A::inline C
    {
        void func()
        {
            std::cout << "C::func()" << std::endl;
        }
    } // namespace C
    
    int main()
    {
        A::func(); // 输出C::func()
        return 0;
    }

    2.21  约束声明的另一种办法

    利用concept与auto的特性,增加了新的约束声明方法。

    例子:

    #include <iostream>
    
    struct Compare
    {
        // 无约束,用auto代替模板类型
        bool operator()(const auto &t1, const auto &t2) const
        { return t1 < t2; }
    };
    
    template<typename T>
    concept CanCompare = requires(T t){
        t * t;  // T类型需要提供*运算符
        Compare().operator()(T(), T()); // 根据Compare结果体,需要T类型提供<运算符
    };
    
    // concept与auto的结合
    CanCompare auto pow2(CanCompare auto x)
    {
        CanCompare auto y = x * x;
        return y;
    }
    
    struct A
    {
        int value = 0;
    
        bool operator<(const A &a) const
        { return value < a.value; }
    
        A operator*(const A &a) const
        { return {.value = a.value * this->value}; }
    };
    
    int main()
    {
        A a;
        a.value = 100;
        A aa = pow2(a);// 推导参数x为A类型,A类型符合CanCompare约束,编译通过
        std::cout << aa.value << std::endl;
        return 0;
    }

    2.22  允许在常量表达式中使用dynamic_cast多态typeid

    待续

    2.23  允许用圆括弧的值进行聚合初始化

    简单地说,就是相当于默认有一个有全部非静态数据成员的构造函数。前提条件:目标类型必须符合聚合初始化的条件。

    例子:

    #include <iostream>
    
    struct A
    {
        int v;
    };
    struct B
    {
        int a;
        double b;
        A &&c;
        long long &&d;
    };
    
    A get() 
    {
        return A();
    }
    
    int main()
    {
        int i = 100;
        B b1{1, 20.0, A(), 200}; // 编译通过
        B b2(1, 20.0, A(), 300); // 编译通过
        B b3{1, 20.0, get(), 300}; // 编译通过
        B b4(2, 30.0, std::move(get()), std::move(i));// 编译通过
        return 0;
    }

    2.24  new表达式的数组元素个数的推导

    从C++20起,new表达式支持数组元素个数的自动推导。

    例子:

    #include <iostream>
    #include <cstring>
    
    int main()
    {
        double a[]{1,2,3}; // 普通的做法
        double *p = new double[]{1,2,3}; // 编译通过
        p = new double[0]{};  // 编译通过
        p = new double[]{}; // 编译通过
        char *d = new char[]{"Hello"}; // 编译通过
        int size = std::strlen(d);
        std::cout << size << std::endl; // 输出5
        return 0;
    }

    2.25  unicode字符串字面量

    新增两种字面量,分别是utf-16和utf-32编码字符串字面量

    例子:

    #include <string>
    
    int main()
    {
        std::u16string str1 = u"aaaaaa"; // 小写u是utf-16字符串
        std::u32string str2 = U"bbbbbb"; // 大写U是utf-32字符串
        return 0;
    }

    2.26  允许转换成未知边界的数组

    这个特性比较简单,在实参为数组的传参时形参可以是无边界的数组。

    例子:

    template<typename T>
    static void func(T (&arr)[]) 
    {
    
    }
    
    template<typename T>
    static void func(T (&&arr)[]) 
    {
        
    }
    
    int main()
    {
        int a[3];
        int b[6];
        func<int>(a);
        func<int>(b);
        func<int>({1, 2, 3, 4});
        func<double>({1.0, 2, 3, 4, 8.0});
        return 0;
    }

    乍一看,好像很鸡肋的特性,不知道数组的长度,长度无法获取,数组的遍历不知道终点,暂时不清楚应用场景。

    2.27  聚合初始化推导类模板参数

    通过聚合初始化中的参数类型 来 推导出类模板参数类型

    例子:

    template <typename T>
    struct S 
    {
        T x;
        T y;
    };
    
    template <typename T>
    struct C 
    {
        S<T> s;
        T t;
    };
    
    template <typename T>
    struct D 
    {
        S<int> s;
        T t;
    };
    
    C c1 = {1, 2}; // error: deduction failed
    C c2 = {1, 2, 3}; // error: deduction failed
    C c3 = {{1u, 2u}, 3}; // OK, C<int> deduced
    D d1 = {1, 2}; // error: deduction failed
    D d2 = {1, 2, 3}; // OK, braces elided, D<int> deduced
    
    template <typename T>
    struct I 
    {
        using type = T;
    };
    
    template <typename T>
    struct E 
    {
        typename I<T>::type i;
        T t;
    };
    
    E e1 = {1, 2}; // OK, E<int> deduced
    

    2.28  隐式地将返回的本地变量转换为右值引用

    在以下的复制操作中,将会隐式采用移动操作代替复制操作的情况:

    ①如果return或co_return中的表达式是一个id-expression,其是在函数的最内层语句块或lambda表达式的主体或者参数声明子句中声明的隐式可移动实体。

    ②throw表达式的一个隐式可移动实体id-expression,其范围不超出最内层try块  或  [复合语句或构造函数初始值包含该throw表达式的函数try块(如果有)] 的复合语句。

    例子:

    #include <iostream>
    
    struct base {
        base() {}
        base(const base &)
        { std::cout << "base(const base &)" << std::endl; }
    private:
        base(base &&)
        { std::cout << "base(base &&)" << std::endl; }
    };
    
    struct derived : base {};
    
    base f() {
        base b;
        throw b; // move
        derived d;
        return d;
    }
    
    int main()
    {
        try
        {
            f();
        }
        catch(base)
        { }
        return 0;
    }

    2.29  允许default修饰运算符按值比较

    直接例子:

    struct C
    {
        // 参数为按值传递
        friend bool operator==(C, C) = default; // C++20起支持
    };

    2.30  非类型模板参数等效的条件

    相同类型的两个值,模板参数等效的条件(之一):

    ①整型且值相同;

    ②浮点类型且值相同;

    ③是std::nullptr_t类型;

    ④枚举类型,且枚举值相同;

    ⑤指针类型,且指针值相同;

    ⑥指向成员的指针类型,且引用相同的类成员,或者都是空成员指针值;

    ⑦引用类型,且引用相同的对象或函数;

    ⑧数组类型,对应元素满足模板参数等效;

    ⑨共用体类型,或者都没有活动成员,或者都具有相同的活动成员,且活动成员都是满足模板参数等效;

    ⑩类类型,且对应的直接子对象和引用成员满足模板参数等效。

     

    3  宏

    无。

    4  属性

    4.1  likely和unlikely

    该属性用于指示switch分支结构的优化,likely表示“很大可能”落到指定分支,而unlikely表示“很小概率”落到指定分支。

    例子:

    int f(int i) 
    {
        switch(i) {
        case 1: [[fallthrough]];
        [[likely]] case 2: return 1;
        [[unlikely]] case 3: return 2;
        }
        return 4;
    }

    4.2  no_unique_address

    这个属性比较的复杂,有以下特性:

    ①同类型的子对象或成员不占用同一个地址;

    ②当地址不够分配时,则按照一般做法扩展空间,继续为未分配地址的no_unique_address属性成员分配地址,直至全部分配完毕;

    ③该属性对空类型(没有非静态数据成员)有效。

    例子1:

    #include <iostream>
    
    struct A
    { };  // 空类型
    
    struct B
    {
        long long v;
        [[no_unique_address]] C a, b;
    };
    
    int main()
    {
        B b;
        std::cout << &b.v << std::endl; // 输出v地址
        std::cout << &b.a << std::endl; // a地址为 &v + 1
        std::cout << &b.b << std::endl; // b地址为 &v + 2
        std::cout << sizeof(B) << std::endl; // 输出 8
        return 0;
    }

    例子2:

    #include <iostream>
    
    struct A
    {  }; // 空对象
    
    struct B
    {
        int v;
        [[no_unique_address]] A a, b, c, d, e, f, g;
    };
    
    int main()
    {
        B b;
        std::cout << &b.v << std::endl; // 得到v地址
        std::cout << &b.a << std::endl; // a地址为 &v + 1
        std::cout << &b.b << std::endl; // a地址为 &v + 2
        std::cout << &b.c << std::endl; // a地址为 &v + 3
        std::cout << &b.d << std::endl; // a地址为 &v + 4
        std::cout << &b.e << std::endl; // a地址为 &v + 5
        std::cout << &b.g << std::endl; // a地址为 &v + 6
        std::cout << &b.f << std::endl; // a地址为 &v + 7
    
        // 由于空间不足,按照一般的内存对齐方式自动扩展空间
        std::cout << sizeof(B) << std::endl; // 输出 8
        return 0;
    }
    

    例子3:

    #include <iostream>
    
    struct A
    { [[no_unique_address]] int value; };
    
    struct B
    {
        int v;
        [[no_unique_address]] A a, b, c;
    };
    
    int main()
    {
        B b;
        std::cout << &b.v << std::endl; // 得到v地址
        std::cout << &b.a << std::endl; // a地址为 &v + 4
        std::cout << &b.b << std::endl; // a地址为 &v + 8
        std::cout << &b.c << std::endl; // a地址为 &v + 12
        std::cout << sizeof(B) << std::endl;// 输出16
        return 0;
    }
    
    

    4.3  nodiscard

    新增可选信息

    例子:

    [[nodiscard("asdfasfa")]] 
    const char * get()
    {
        return "";
    }
    
    int main()
    {
        get(); // warning: ignoring return value of 'const char* get()', declared with attribute 'nodiscard': 'asdfasfa' [-Wunused-result]
        return 0;
    }

    5  弃用

    5.1  lambda弃用使用[=]来隐式捕获this

    struct X 
    {
        int x;
        void foo(int n) 
        {
            auto f = [=]() { x = n; };         // 弃用:此处的x是this->x,而非拷贝
            auto g = [=, this]() { x = n; };   // 新版推荐的方法
        }
    };

    5.2  比较运算符的改进

    ①弃用枚举的隐式算术转换

    enum E1 { e };
    enum E2 { f };
    
    int main()
    {
        bool b = e <= 3.7;    // deprecated
        int k = f - e;        // deprecated
        auto cmp = e <=> f;   // ill-formed
    
        return 0;
    }

    ②数组的比较

    int arr1[5];
    int arr2[5];
    bool same = arr1 == arr2;   // deprecated, 效果与&arr1[0] == &arr2[0]相同,并非比较数组内容
    auto cmp = arr1 <=> arr2;   // ill-formed
    

    5.3  弃用下标表达式中的逗号操作符

    在下标访问时,弃用逗号分隔的多个参数的语法。

    如例子:

    int main()
    {
        int a[3]{0, 1, 3};
        // 在如下的逗号操作符中,只保留最后一个有效,这个特性不变
        int tmp1 = a[4, 1]; // tmp1 = a[1] = 1
        int tmp2 = a[10, 1, 2]; // tmp2 = a[2] = 3
        return 0;
    }

    后记

    关于C++20新特性的英文文档本人已提供免费下载,感兴趣的可以自行下载:https://download.csdn.net/download/qq811299838/12554178

    我的内容只是展示应用层面,而不对新特性的目标进行阐述,因为这样子可以少写很多字。

    另外,如有问题,欢迎指出。

    展开全文
  • C++新特性 强制转换const_cast

    千次阅读 2021-02-26 22:19:19
    待补充
  • 信息科学技术学院《程序设计实习》 ,C++新标准11新特性专讲
  • C++11_14新特性PPT

    2017-11-17 16:37:57
    Scott Meyers博士的上课PPT,清晰简洁的描述了现代C++的常用特性
  • 主要介绍了C++14新特性的所有知识点全在这,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • c++最近版本的新特性概览

    千次阅读 2020-05-10 09:06:36
    参考1:c++17特性 这个比较偏,主要包括auto关键字和varient 参考2:c++17特性:标准库 包括 1)std::function 2)array 3) 元组tuple:很少用 4) c++17直接使用std::size()函数获得数组的大小 5)string.data()...
  • 想学习C++11的同学赶紧来下载,可以让你对C++11的新特性进行深入的了解和学习,C++11对性能和易用性可是大大的提升哟,赶紧行动吧。
  • C++11常用新特性汇总

    万次阅读 多人点赞 2018-12-17 14:53:18
    C++11已经出来很久了,网上也早有很多优秀的C++11新特性的总结文章,在编写本博客之前,博主在工作和学习中学到的关于C++11方面的知识,也得益于很多其他网友的总结。本博客文章是在学习的基础上,加上博主在日常...
  • 深入理解C++11这本书比较详细介绍了C++新特性,中国人编写的技术书,语言通俗易懂,不仅讲解了C++11新特性,而且还有大量的C++基础知识,是提升C++语言技能的难得好书。
  • 《深入理解C++11:C++11新特性解析与应用》基本信息作者: Michael Wong IBM XL编译器中国开发团队 丛书名: 原创精品系列出版社:机械工业出版社ISBN:9787111426608上架时间:2013-5-31出版日期:2013 年6月开本...
  • C++17新特性

    2018-04-23 14:00:18
    C++17开发中新的特性,对我们现有的影响以及改善,了解C++17特性
  • C++11 新特性

    2016-11-03 00:22:46
    深入理解C++11:C++11新特性解析与应用 清晰PDF
  • c++新特性:多线程

    千次阅读 2015-03-05 15:45:24
    很高兴c++11的标准库可以#include <thread>了。boost早就提供了类似功能。这时候考虑下开发商、物业公司联合阻碍成立业主委员会的借口之一: 会妨碍事情的正常进展,推断也许他们也是c++的长期使用者:) 1、...
  • C++11常用新特性快速一览

    万次阅读 多人点赞 2018-02-23 19:28:13
    最近工作中,遇到一些问题,使用C++11实现起来会更加方便,而线上的生产环境还不支持C++11,于是决定新年开工后,在组内把C++11推广开来,整理以下文档,方便自己查阅,也方便同事快速上手。(对于异步编程十分实用...
  • C++面试知识】C++11新特性

    千次阅读 2019-05-30 11:14:57
    1.的keyword (1)auto 自动类型推导: auto a; // 错误,auto是通过初始化表达式进行类型推导,假设没有初始化表达式,就无法确定a的类型 auto i = 1; auto d = 1.0; auto str = "Hello World"; auto ch = 'A...
  • 带完全的书签,适合于有C,C++基础的开发者,对于了解以及使用C++11的新特性,提高程序性能有一定有帮助。 深入理解C++11新特性解析与应用(高清完整PDF带目录+配套源码),高清完整PDF带目录电子书带配套的源码,...
  • 熟悉脚本语言的人都知道,很多脚本语言都引入了“类型自动推断”技术:比如Python,可以直接声明变量,在运行时进行类型检查。...这篇文章主要介绍了C++11新特性之auto的使用,有需要的朋友们可以参考借鉴。
  • C++14 常用新特性总结

    千次阅读 2018-04-14 11:59:58
    1. 返回值类型推导(Return type deduction)为什么返回类型推导对于C++程序来说是锦上添花的。首先,有时候你必须返回一个非常复杂的类型,比如在对标准库容器进行搜索的时候返回一个迭代器。auto返回类型使得函数...
  • 在项目的.pro文件里面添加 【方法一】 QMAKE_CXXFLAGS+= -std=c++11 【方法二】 CONFIG+= c++11
  • C++标准委员会成员和IBM XL编译器中国开发团队共同撰写,权威性毋庸置疑。系统、深入、详尽地讲解了C++11标准中的语言特性标准库特性、对原有特性的改进,以及所有这些新特性的应用。
  • C++11新特性解析与应用》完整版pdf

    热门讨论 2014-01-13 15:08:49
    比如,像 auto 类型推导这样的新特性,展现出的是语言的亲和力 ; 而右值引用、移动语义的特性,则着重于改变一些使用 C++ 程序库时容易发生的性能不佳的 状况。当然,C++11 中也有局部的创新,比如 lambda 函数的...
  • 标准委员会成员和IBM XL编译器中国开发团队共同撰写,性毋庸置疑 系统、深入、详尽地讲解了C 11标准中的语言特性标准库特性、对原有特性的改进,以及所有这些新特性的应用

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 373,304
精华内容 149,321
关键字:

c++新特性

c++ 订阅