C++17中那些值得关注的特性

C++17标准在2017上半年已经讨论确定,正在形成ISO标准文档,今年晚些时候会正式发布。本文将介绍最新标准中值得开发者关注的新特新和基本用法。

我有几张阿里云幸运券分享给你,用券购买或者升级阿里云相应产品会有特惠惊喜哦!把想要买的产品的幸运券都领走吧!快下手,马上就要抢光了。

总的来说C++17相比C++11的新特性来说新特性不算多,做了一些小幅改进。C++17增加了数十项新特性,值得关注的特性大概有下面这些:

constexpr if

constexpr lambda

fold expression

void_t

structured binding

std::apply, std::invoke

string_view

parallel STL

inline variable

剩下的有一些来自于boost库,比如variant,any、optional和filesystem等特性,string_view其实在boost里也有。还有一些是语法糖,比如if init、deduction guide、guaranteed copy Elision、template、nested namespace、single param static_assert等特性。我接下来会介绍C++17主要的一些特性,介绍它们的基本用法和作用,让读者对C++17的新特性有一个基本的了解。

fold expression

C++11增加了一个新特性可变模版参数(variadic template),它可以接受任意个模版参数在参数包中,参数包是三个点…,它不能直接展开,需要通过一些特殊的方法才能展开,导致在使用的时候有点难度。现在C++17解决了这个问题,让参数包的展开变得容易了,Fold expression就是方便展开参数包的。

fold expression的语义

fold expression有4种语义:

unary right fold (pack op …)

unary left fold (… op pack)

binary right fold (pack op … op init)

binary left fold (init op … op pack)

其中pack代表变参,比如args,op代表操作符,fold expression支持32种操作符:

+ - * / % ^ & | =< ><< >> += -= *= /= %= ^= &= |=<<= >>= == !=<=>= && || , .* ->*如果unary right fold的含义

fold (E op …) 意味着 E1op (… op (EN-1op EN)).

顾名思义,从右边开始fold,看它是left fold还是right fold我们可以根据参数包…所在的位置来判断,当参数包…在操作符右边的时候就是right fold,在左边的时候就是left fold。我们来看一个具体的例子:

template

auto add_val(Args&&... args) {

return (args +  ...);

}

auto t = add_val(1,2,3,4); //10

right fold的过程是这样的:(1+(2+(3+4))),从右边开始fold。

如果你想学习C/C++可以来这个群,首先是330,中间是859,最后是766,里面可以学习和交流,也有资料可以下载。

unary left fold的含义

fold (… op E) 意味着 ((E1op E2) op …) op EN。

对于+这种满足交换律的操作符来说left fold和right fold是一样的,比如上面的例子你也可以写成left fold。

template

auto add_val(Args&&... args) {

return (... + args);

}

auto t = add_val(1,2,3,4); //10

对于不满足交换律的操作符来说就要注意了,比如减法。

template

auto sub_val_right(Args&&... args) {

return (args - ...);

}

template

auto sub_val_left(Args&&... args) {

return (... - args);

}

auto t = sub_val_right(2,3,4); //(2-(3-4)) = 3

auto t1 = sub_val_left(2,3,4); //((2-3)-4) = -5

这次right fold和left fold的结果就不一样。

binary fold的含义

Binary right fold (E op … op I) 意味着 E1op (… op (EN-1op (ENop I)))。

Binary left fold (I op … op E) 意味着 (((I op E1) op E2) op …) op E2。

其中E代表变参,比如args,op代表操作符,I代表一个初始变量。

二元fold的语义和一元fold的语义是相同的,看一个二元操作符的例子:

template

auto sub_one_left(Args&&... args) {

return (1 - ... - args);

}

template

auto sub_one_right(Args&&... args) {

return (args - ... - 1);

}

auto t = sub_one_left(2,3,4);// (((1-2)-3)-4) = -8

auto t1 = sub_one_right(2,3,4);//(2-(3-(4-1))) = 2

相信通过这个例子大家应该对C++17的fold expression有了基本的了解。

comma fold

在C++17之前,我们经常使用逗号表达式和std::initializer_list来将变参一个个传入一个函数。比如像下面这个例子:

template

void print_arg(T t)

{

std::cout << t << std::endl;

}

template

void print2(Args... args)

{

//int a[] = { (printarg(args), 0)... };

std::initializer_list{(print_arg(args), 0)...};

}

这种写法比较繁琐,用fold expression就会变得很简单了。

template

void print3(Args... args)

{

(print_arg(args), ...);

}

这是right fold,你也可以写成left fold,对于comma来说两种写法是一样的,参数都是从左至右传入print_arg函数。

template

void print3(Args... args)

{

(..., print_arg(args));

}

你也可以通过binary fold这样写:

template

void printer(Args&&... args) {

(std::cout << ... << args) << '\n';

}

也许你会觉得能写成这样:

template

void printer(Args&&... args) {

(std::cout << args << ...) << '\n';

}

但这样写是不合法的,根据binary fold的语法,参数包…必须在操作符中间,因此上面的这种写法不符合语法要求。

借助comma fold我们可以简化代码,假如我们希望实现tuple的for_each算法,像这样:

for_each(std::make_tuple(2.5, 10, 'a'),[](auto e) { std::cout << e<< '\n'; });

这个for_each将会遍历tuple的元素并打印出来。在C++17之前我们如果要实现这个算法的话,需要借助逗号表达式和std::initializer_list来实现,类似于这样:

template

void for_each(const std::tuple& t, Func&& f, std::index_sequence) {

(void)std::initializer_list { (f(std::get(t)), void(), 0)...};

}

这样写比较繁琐不直观,现在借助fold expression我们可以简化代码了。

template

void for_each(const std::tuple& t, Func&& f, std::index_sequence) {

(f(std::get(t)), ...);

}

借助coma fold我们可以写很简洁的代码了。

constexpr if

constexpr标记一个表达式或一个函数的返回结果是编译期常量,它保证函数会在编译期执行。相比模版来说,实现编译期循环或递归,C++17中的constexpr if会让代码变得更简洁易懂。比如实现一个编译期整数加法:

template

constexpr int sum()

{

return N;

}

template

constexpr int sum()

{

return N + sum();

}

C++17之前你可能需要像上面这样写,但是现在你可以写更简洁的代码了。

template

constexpr auto sum17()

{

if constexpr (sizeof...(Ns) == 0)

return N;

else

return N + sum17();

}

当然,你也可以用C++17的fold expression:

template

constexpr int sum(Args... args) {

return (0 + ... + args);

}

constexpr还可以用来消除enable_if了,对于讨厌写一长串enable_if的人来说会非常开心。比如我需要根据类型来选择函数的时候:

template

std::enable_if_t::value, std::string> to_str(T t)

{

return std::to_string(t);

}

template

std::enable_if_t::value, std::string> to_str(T t)

{

return t;

}

经常不得不分开几个函数来写,还需要写长长的enable_if,比较繁琐,通过if constexpr可以消除enable_if了。

template

auto to_str17(T t)

{

if constexpr(std::is_integral::value)

return std::to_string(t);

else

return t;

}

constexpr if让C++的模版具备if-else if-else功能了,是不是很酷,C++程序员的好日子来了。

不过需要注意的是下面这种写法是有问题的。

template

auto to_str17(T t)

{

if constexpr(std::is_integral::value)

return std::to_string(t);

return t;

}

这个代码把else去掉了,当输入如果是非数字类型时代码可以编译过,以为if constexpr在模版实例化的时候会丢弃不满足条件的部分,因此函数体中的前两行代码将失效,只有最后一句有效。当输入的为数字的时候就会产生编译错误了,因为if constexpr满足条件了,这时候就会有两个return了,就会导致编译错误。

constexpr if还可以用来替换#ifdef宏,看下面的例子

enum class OS { Linux, Mac, Windows };

//Translate the macros to C++ at a single point in the application

#ifdef __linux__

constexpr OS the_os = OS::Linux;

#elif __APPLE__

constexpr OS the_os = OS::Mac;

#elif __WIN32

constexpr OS the_os = OS::Windows;

#endif

void do_something() {

//do something general

if constexpr (the_os == OS::Linux) {

//do something Linuxy

}

else if constexpr (the_os == OS::Mac) {

//do something Appley

}

else if constexpr (the_os == OS::Windows) {

//do something Windowsy

}

//do something general

}

//备注:这个例子摘自https://blog.tartanllama.xyz/c++/2016/12/12/if-constexpr/

代码变得更清爽了,再也不需要像以前一样写#ifdef那样难看的代码块了。

constexpr lambda

constexpr lambda其实很简单,它的意思就是可以在constexpr 函数中用lambda表达式了,这在C++17之前是不允许的。这样使用constexpr函数和普通函数没多大区别了,使用起来非常舒服。下面是constexpr lambda的例子:

template

constexpr auto func(I i) {

//use a lambda in constexpr context

return [i](auto j){ return i + j; };

}

constexpr if和constexpr lambda是C++17提供的非常棒的特性,enjoy it.

string_view

string_view的基本用法

C++17中的string_view是一个char数据的视图或者说引用,它并不拥有该数据,是为了避免拷贝,因此使用string_view可以用来做性能优化。你应该用string_view来代替const char和const string了。string_view的方法和string类似,用法很简单:

const char* data = "test";

std::string_view str1(data, 4);

std::cout<

if(data==str1)

std::cout<<"ok"<<'\n';

const std::string str2 = "test";

std::string_view str3(str2, str2.size());

构造string_view的时候用char*和长度来构造,这个长度可以自由确定,它表示string_view希望引用的字符串的长度。因为它只是引用其他字符串,所以它不会分配内存,不会像string那样容易产生临时变量。我们通过一个测试程序来看看string_view如何来帮我们优化性能的。

using namespace std::literals;

constexpr auto s = "it is a test"sv;

auto str = "it is a test"s;

constexpr int LEN = 1000000;

boost::timer t;

for (int i = 0; i < LEN; ++i) {

constexpr auto s1 = s.substr(3);

}

std::cout<

t.restart();

for (int i = 0; i < LEN; ++i) {

auto s2 = str.substr(3);

}

std::cout<

//output

0.004197

0.231505

我们可以通过字面量””sv来初始化string_view。string_view的substr和string的substr相比,快了50多倍,根本原因是它不会分配内存。

string_view的生命周期

由于string_vew并不拥有锁引用的字符串,所以它也不会去关注被引用字符串的生命周期,用户在使用的时候需要注意,不要将一个临时变量给一个string_view,那样会导致string_view引用的内容也失效。

std::string_view str_v;

{

std::string temp = "test";

str_v = {temp};

}

这样的代码是有问题的,因为出了作用域之后,string_view引用的内容已经失效了。

总结

本文介绍了C++17的fold expression、constexpr if、constexpr lambda和string_view。fold expression为了简化可变模板参数的展开,让可以模板参数的使用变得更简单直观;constexpr if让模板具备if-else功能,非常强大。它也避免了写冗长的enable_if代码,让代码变得简洁易懂了;string_view则是用来做性能优化的,应该用它来代替const char*和const string。

阅读原文:http://click.aliyun.com/m/35041/

If you’ve ever asked “what’s in C++17 and what does it mean for me and my code?” — and I hope you have — then this book is for you. Now that the C++ standard is being released regularly every three years, one of the challenges we have as a community is learning and absorbing the new features that are being regularly added to the standard language and library. That means not only knowing what those features are, but also how to use them effectively to solve problems. Bartlomiej Filipek does a great job of this by not just listing the features, but explaining each of them with examples, including a whole Part 3 of the book about how to apply key new C++17 features to modernize and improve existing code — everything from upgrading enable_if to the new if constexpr, to refactoring code by applying the new optional and variant vocabulary types, to writing parallel code using the new standard parallel algorithms. In each case, the result is cleaner code that’s often also significantly faster too. The point of new features isn’t just to know about them for their own sake, but to know about how they can let us express our intent more clearly and directly than ever in our C++ code. That ability to directly “say what we mean” to express our intent, or to express “what” we want to achieve rather than sometimes-tortuous details of “how” to achieve it through indirect mechanisms, is the primary thing that determines how clean and writable and readable — and correct — our code will be. For C++ programmers working on real-world projects using reasonably up-to-date C++ compilers, C++17 is where it’s at in the industry today for writing robust production code. Knowing what’s in C++17 and how to use it well is an important tool that will elevate your day-to-day coding, and more likely than not reduce your day-to-day maintenance and debugging chores. If you’re one of the many who have enjoyed Barteks’s blog (bfilipek.com, frequently cited at isocpp.org), you’ll certainly also enjoy this entertaining and informative book. And if you haven’t enjoyed his blog yet, you should check it out too… and then enjoy the book.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值