C++17特性一览

引言

这一系列的第二篇文章。但是其实我并不想写第三篇,也就是描述C++20的特性了,现在这个时间点GCC最新的版本就是10.2,安装以后发现还是没有办法编译C++20,也可能是我的方法不太对,但是确实是让人非常的烦,写不了代码介绍它作甚?况且,反正未来五六年内各大公司也不会上线C++20吧。

再说回C++17,这次的更新比C++14要大不少,其中很多东西都可以为我们的C++代码在优化代码可读性的同时提高效率,我们一起来看看吧
在这里插入图片描述

构造函数模板推导

其实我更愿意叫它模板类型自动推导,代码展示大概就是这样:

 vector items = {1,2,3};
 // pair pa(4, "string"); 不直接匹配字符串
 pair pa(4, string("hihi"));
 cout << pa.second << " " << items[2] << endl;
 输出:
 hihi 3

结构化绑定

可以绑定pair,tuple,数组,结构体,结构化绑定以后也可以修改原值,也可以使自定义类型支持结构化绑定,但是要修改std,感觉没什么必要。

std::tuple<int, double> func_two() {
    return std::tuple(1, 2.2);
}

auto[i, d] = func_two();
cout << i << " " << d << endl;

map<int, string> mp = {
  {0, "a"},
  {1, "b"},
};

for(const auto& [x, y] : mp){
  cout << x << " " << y << endl;
}

pair pa(4, string("hihi"));
auto&[x, y] = pa;
cout << x << " " << y << endl;

输出:
1 2.2
0 a
1 b
4 hihi

/*       vector vec = {1,2,3};
      auto&[xx, yy, zz] = vec;
      cout << xx << endl; */ vector当然不能使用结构化绑定啦

if-switch语句初始化

变量的作用域划分的更明显了。

if (int a = 29; a < 101) {
    cout << a;
}

内联变量

这是个非常有意思的东西,你可能会觉得函数inline可以提高效率,变量inline有什么用?这其实涉及到inline的一个一般不广为人所知的特性,即多个翻译单元内的重复声明C++链接器只选择一个,这意味这有inline声明的函数和变量我们可以声明在头文件中了

代码来看就是这样:

// header file
struct A {
    static const int value;  
};
inline int const A::value = 10;

// ==========或者========
struct A {
    inline static const int value = 10;
}

有兴趣的朋友可以看看下面两篇文章:

c++ inline variable 内联变量 c++17
GCC,Clang 在C模式,较低优化等级下,链接器对内联函数报未定义错误,为什么?

折叠表达式

template <typename ... TT> 
constexpr auto foldSumRec(T... arg) {
    return (arg + ...);
}

C++17以前得写成这样:

template<typename T>
auto foldSumRec (T arg) 
{
    return arg;
}

template<typename T1, typename... Ts>
auto foldSumRec (T1 arg1, Ts... otherArgs) 
{
    return arg1 + foldSumRec(otherArgs...);
}

具体的细节可查看这篇文章《C++17 fold expression

constexpr lambda表达式

需要注意的是有如下约束:函数体不能包含汇编语句、goto语句、label、try块、静态变量、线程局部存储、没有初始化的普通变量,不能动态分配内存,不能有new delete等,不能使用虚函数

 constexpr auto lamb = [](auto b) {
     int ret = 0;  // C++14中放宽了lambda的标准
     for (size_t i = 0; i < b; i++){
         ret += i;
     }
     return ret;
 };

namespace嵌套

namespace A {
    namespace B {
        namespace C {
            void func();
        }
    }
}

// c++17
namespace A::B::C {
    void func();
}

语法糖,更方便更舒适

from_chars函数和to_chars

具体可参考《C++标准库里自带的数值类型和字符串互相转换函数

std::array<char, 3> str{"42"};
int result;
std::from_chars( str.data(), str.data()+str.size(),result );
std::cout << result << std::endl;

// p是填充到str以后的最后一个迭代器
if(auto [p, ec] = std::to_chars(str.data(), str.data() + str.size(), 425);
   ec == std::errc()){
    if(p == str.end()){
        std::cout << "hello world\n";
    }
        std::cout << std::string_view(str.data(), p - str.data());
}
输出:
42
hello world
425

std::shared_mutex

千盼万盼你终于是来了。。这个不需要解释了,就是读写锁了。

关于与mutex的性能对比可以看这里《std::shared_mutex和std::mutex的性能对比(benchmark)

std::variant

具体可参考《C++17之std::variant

struct NoDefConstr_seven{
    NoDefConstr_seven(int i){
        std::cout << "NoDefConstr::NoDefConstr(int) called\n";
    }
};
// variant类似于union,第一个参数必须拥有默认构造函数

std::variant<int, std::string> var{"hi"}; // initialized with string alternative
std::cout << var.index() << std::endl; // prints 1
var = 42; // now holds int alternative
std::cout << var.index() << std::endl; // prints 0
try {
  std::string s = std::get<std::string>(var); // access by type
  int i = std::get<0>(var); // access by index
}
catch (const std::bad_variant_access& e) { // in case a wrong type/index is used
  std::cout << "hello\n";
}

// std::variant<NoDefConstr_seven, int> v1; 第一个参数没有构造函数 编译失败
// std::monostate就是防止全部的参数都没有默认构造函数
std::variant<std::monostate, NoDefConstr_seven, int> v2;

输出:
1
0
hello

std::optional

具体可参考《C++干货系列——C++17新特性之std::optional

其实就是为了防止我们平时在代码中返回一个不存在语义时随便设置的magic number。

std::any

一般顶多variant就足够用了,何必用Any呢 但在极端情况下,用any总比用void*强得多,鼓励实现避免小对象的动态分配

std::any a = 1;
cout << a.type().name() << " " << std::any_cast<int>(a) << endl;
a = 2.2f;
cout << a.type().name() << " " << std::any_cast<float>(a) << endl;
if (a.has_value()) {
    cout << a.type().name() << std::endl;
}
a.reset();
// 可以这样判断类型
// assert(a1.type() == typeid(int));
if (a.has_value()) {
    cout << a.type().name() << std::endl;
}
a = std::string("a");
// 这个string的类型名是真的恶心
cout << a.type().name() << ": " << std::any_cast<std::string>(a) << endl;

输出:
i 1
f 2.2
f
NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE: a

具体可查看这篇文章《C++17之std::any

std::apply

我认为其实就是把容器的值当做函数的输入

int add_ten(int first, int second) { return first + second; }
auto add_ten_lambda = [](auto first, auto second) { return first + second; };

std::cout << std::apply(add_ten, std::pair(1, 2)) << '\n';
//std::cout << add(std::pair(1, 2)) << "\n"; // error
std::cout << std::apply(add_ten_lambda, std::tuple(2.2f, 3.0f)) << '\n';

输出:
3
5.2

具体可参考《使用std :: apply应用可变参数函数(Applying a variadic function with std::apply)

std::make_from_tuple

使用make_from_tuple可以将tuple展开作为构造函数参数:

struct Foo {
    Foo(int first, float second, int third) {
        std::cout << first << ", " << second << ", " << third << "\n";
    }
};
int main() {
   auto tuple = std::make_tuple(42, 3.14f, 0);
   std::make_from_tuple<Foo>(std::move(tuple));
}

std::string_view

平时代码中可以大规模使用的一个特性。其实对于string的争论一直没有停止过,很多人认为string是字节串而不是字符串,因为string是可以改变的,这一切争论到C++17可以停止了。string_view的substr与构造时间复杂度为O(1),且不会产生拷贝,因为substr只是一个指针操作。

可以参考如下两篇文章:
C++17,使用 string_view 来避免复制
C++17 string_view的高效以及陷阱

std::file_system

具体可参考《c++ filesystem

这其实模子是boost的file_system,最早2003年就出来了,因为是跨平台的,所以可以说是非常舒服了。

有一说一,在我的机子上跑不了这个。

代码:

namespace fs = std::filesystem;
fs::path pathToShow("/home/lzl/Desktop/execise");
cout << "exists() = " << fs::exists(pathToShow) << "\n"
<< "root_name() = " << pathToShow.root_name() << "\n"
<< "root_path() = " << pathToShow.root_path() << "\n";

跑完以后显示这样:

./a.out: /lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.26' not found (required by ./a.out)

并行算法库

这可以说是C++17最重要的几个特性之一,这个特性为几乎所有标准库函数加上一个执行策略参数,可以让使用者选择并行还是串行,这不仅包括七个新的算法,还有我们熟知的sort等。

具体可参考:
[译]C++17,标准库新引入的并行算法
STL并行算法库

constexpr if

在C++17以前泛型函数中是不可以出现if这样的逻辑判断符的

template <int N, int... Ns>
auto sum()
{
    if (sizeof...(Ns) == 0) // 若参数包为空, 直接返回 N
        return N;
    else                    // 否则进行递归调用
        return N + sum<Ns...>();
}

这样就会编译失败。

以前的做法是用模板递归,就像这样:

// 只有一个模板参数时调用此模板
template<int N>
int sum()
{
    return N;
}

// 模板参数 > 2 个时调用此模板
template <int N, int N2, int... Ns>
int sum()
{
    return N + sum<N2, Ns...>();
}

现在我们可以这样:

template <int N, int... Ns>
auto sum()
{
    if constexpr (0 == sizeof...(Ns))
        return N;
    else
        return N + sum<Ns...>();
}

具体可参考:《C++17 之 “constexpr if”

参考:

posted @ 2022-07-02 13:16  李兆龙的博客  阅读(265)  评论(0编辑  收藏  举报