在本章中,您将学习以下概念:
- C++ 17 背景
- C++ 17 有什么新功能?
- C++ 17 中有哪些功能被弃用或删除了?
- C++ 17 中的关键特性
众所周知,C++ 语言是比雅尼·斯特劳斯特鲁普的大脑产物,他在 1979 年开发了 C++。C++ 编程语言由国际标准化组织(ISO)标准化。
最初的标准化发布于 1998 年,通常称为 C++ 98,下一个标准化 C++ 03 发布于 2003 年,它主要是一个 bug 修复版本,只有一个用于值初始化的语言特性。2011 年 8 月,C++ 11 标准发布,对核心语言做了几处补充,包括对标准模板库 ( STL )做了几处有意义的改动;C++ 11 基本取代了 C++ 03 标准。C++ 14 于 2014 年 12 月发布,新增了一些功能,后来 C++ 17 标准于 2017 年 7 月 31 日发布。
在撰写本书时,C++ 17 是针对 C++ 编程语言的 ISO/IEC 标准的最新修订版。
本章要求编译器支持 C++ 17 特性:gcc 版本 7 或更高版本。由于 gcc 版本 7 是撰写本书时的最新版本,所以我将在本章中使用 gcc 版本 7.1.0。
In case you haven't installed g++ 7 that supports C++ 17 features, you can install it with the following commands:
sudo add-apt-repository ppa:jonathonf/gcc-7.1 sudo apt-get update sudo apt-get install gcc-7 g++-7
C++ 17 特性的完整列表可以在http://en . CP preference . com/w/CPP/compiler _ support # C . 2b . 2b 17 _ 特性找到。
为了给出一个高层次的想法,下面是一些新的 C++ 17 特性:
- 直接列表初始化的新自动规则
static_assert
无消息- 嵌套命名空间定义
- 内联变量
- 命名空间和枚举器的属性
- C++ 异常规范是类型系统的一部分
- 改进的 lambda 功能为服务器带来了性能优势
- NUMA 建筑
- 使用属性命名空间
- 过度对齐数据的动态内存分配
- 类模板的模板参数推导
- 自动类型的非类型模板参数
- 保证复制省略
- 继承构造函数的新规范
- 枚举的直接列表初始化
- 更严格的表达式求值顺序
shared_mutex
- 字符串转换
除此之外,还有许多有趣的新特性被添加到核心 C++ 语言中:STL、lambadas 等等。新功能让 C++ 焕然一新,从C++ 17
开始,作为一名 C++ 开发人员,你会觉得自己在用一种现代编程语言工作,比如 Java 或者 C#。
现在,C++ 17 中删除了以下功能:
register
关键字在 C++ 11 中被弃用,在 C++ 17 中被删除bool
的++
运算符在 C++ 98 中被弃用,在 C++ 17 中被移除- 动态异常规范在 C++ 11 中被弃用,在 C++ 17 中被删除
让我们在以下几节中逐一探讨以下 C++ 17 关键特性:
- 更简单的嵌套命名空间
- 从支撑初始化列表中检测类型的新规则
- 简化
static_assert
std::invoke
- 结构化绑定
if
和switch
局部变量- 类模板的模板类型自动检测
- 内联变量
直到 C++ 14 标准,C++ 中嵌套命名空间支持的语法如下:
#include <iostream>
using namespace std;
namespace org {
namespace tektutor {
namespace application {
namespace internals {
int x;
}
}
}
}
int main ( ) {
org::tektutor::application::internals::x = 100;
cout << "\nValue of x is " << org::tektutor::application::internals::x << endl;
return 0;
}
可以使用以下命令编译前面的代码并查看输出:
g++-7 main.cpp -std=c++ 17
./a.out
前面程序的输出如下:
Value of x is 100
每个命名空间级别都以花括号开始和结束,这使得在大型应用中很难使用嵌套的命名空间。C++ 17 嵌套命名空间语法真的很酷;只要看看下面的代码,你就会很容易同意我的观点:
#include <iostream>
using namespace std;
namespace org::tektutor::application::internals {
int x;
}
int main ( ) {
org::tektutor::application::internals::x = 100;
cout << "\nValue of x is " << org::tektutor::application::internals::x << endl;
return 0;
}
可以使用以下命令编译前面的代码并查看输出:
g++-7 main.cpp -std=c++ 17
./a.out
输出与前一个程序相同:
Value of x is 100
C++ 17 引入了初始化列表自动检测的新规则,补充了 C++ 14 的规则。C++ 17 规则坚持认为,如果声明了std::initializer_list
的显式或部分专门化,则程序是不良的:
#include <iostream>
using namespace std;
template <typename T1, typename T2>
class MyClass {
private:
T1 t1;
T2 t2;
public:
MyClass( T1 t1 = T1(), T2 t2 = T2() ) { }
void printSizeOfDataTypes() {
cout << "\nSize of t1 is " << sizeof ( t1 ) << " bytes." << endl;
cout << "\nSize of t2 is " << sizeof ( t2 ) << " bytes." << endl;
}
};
int main ( ) {
//Until C++ 14
MyClass<int, double> obj1;
obj1.printSizeOfDataTypes( );
//New syntax in C++ 17
MyClass obj2( 1, 10.56 );
return 0;
}
可以使用以下命令编译前面的代码并查看输出:
g++-7 main.cpp -std=c++ 17
./a.out
前面程序的输出如下:
Values in integer vectors are ...
1 2 3 4 5
Values in double vectors are ...
1.5 2.5 3.5
static_assert
宏有助于在编译时识别断言失败。从 C++ 11 开始就支持这个特性;然而,static_assert
宏过去接受强制断言失败消息,现在在 C++ 17 中变成可选的。
以下示例演示了带有和不带有消息的static_assert
的使用:
#include <iostream>
#include <type_traits>
using namespace std;
int main ( ) {
const int x = 5, y = 5;
static_assert ( 1 == 0, "Assertion failed" );
static_assert ( 1 == 0 );
static_assert ( x == y );
return 0;
}
前面程序的输出如下:
g++-7 staticassert.cpp -std=c++ 17
staticassert.cpp: In function ‘int main()’:
staticassert.cpp:7:2: error: static assertion failed: Assertion failed
static_assert ( 1 == 0, "Assertion failed" );
staticassert.cpp:8:2: error: static assertion failed
static_assert ( 1 == 0 );
从前面的输出中,您可以看到消息Assertion failed
作为编译错误的一部分出现,而在第二次编译中,默认的编译器错误消息出现,因为我们没有提供断言失败消息。当没有断言失败时,断言错误消息不会出现,如static_assert ( x == y )
所示。这个特性的灵感来自于 BOOST C++ 库中的 C++ 社区。
std::invoke()
方法可以用相同的语法调用函数、函数指针和成员指针:
#include <iostream>
#include <functional>
using namespace std;
void globalFunction( ) {
cout << "globalFunction ..." << endl;
}
class MyClass {
public:
void memberFunction ( int data ) {
std::cout << "\nMyClass memberFunction ..." << std::endl;
}
static void staticFunction ( int data ) {
std::cout << "MyClass staticFunction ..." << std::endl;
}
};
int main ( ) {
MyClass obj;
std::invoke ( &MyClass::memberFunction, obj, 100 );
std::invoke ( &MyClass::staticFunction, 200 );
std::invoke ( globalFunction );
return 0;
}
可以使用以下命令编译前面的代码并查看输出:
g++-7 main.cpp -std=c++ 17
./a.out
前面程序的输出如下:
MyClass memberFunction ...
MyClass staticFunction ...
globalFunction ...
std::invoke( )
方法是一个模板函数,帮助您无缝调用内置和用户定义的可调用对象。
现在,您可以用非常酷的语法用返回值初始化多个变量,如下面的代码示例所示:
#include <iostream>
#include <tuple>
using namespace std;
int main ( ) {
tuple<string,int> student("Sriram", 10);
auto [name, age] = student;
cout << "\nName of the student is " << name << endl;
cout << "Age of the student is " << age << endl;
return 0;
}
在前面的程序中,粗体突出显示的代码是 C++ 17 中引入的结构化绑定特性。有趣的是,我们没有声明string name
和int age
变量。这些都是由 C++ 编译器自动推导出来的string
和int
,这使得 C++ 的语法就像任何现代编程语言一样,不会失去其性能和系统编程的好处。
可以使用以下命令编译前面的代码并查看输出:
g++-7 main.cpp -std=c++ 17
./a.out
前面程序的输出如下:
Name of the student is Sriram
Age of the student is 10
有一个有趣的新特性,允许你声明一个局部变量绑定到if
和switch
语句的代码块。在if
和switch
语句中使用的变量的范围将超出各自块的范围。通过一个简单易懂的例子可以更好地理解,如下所示:
#include <iostream>
using namespace std;
bool isGoodToProceed( ) {
return true;
}
bool isGood( ) {
return true;
}
void functionWithSwitchStatement( ) {
switch ( auto status = isGood( ) ) {
case true:
cout << "\nAll good!" << endl;
break;
case false:
cout << "\nSomething gone bad" << endl;
break;
}
}
int main ( ) {
if ( auto flag = isGoodToProceed( ) ) {
cout << "flag is a local variable and it loses its scope outside the if block" << endl;
}
functionWithSwitchStatement();
return 0;
}
可以使用以下命令编译前面的代码并查看输出:
g++-7 main.cpp -std=c++ 17
./a.out
前面程序的输出如下:
flag is a local variable and it loses its scope outside the if block
All good!
我相信您会喜欢您即将在示例代码中看到的内容。虽然模板很有用,但是很多人不喜欢它,因为它的语法很难理解,也很奇怪。但是你不用再担心了;看看下面的代码片段:
#include <iostream>
using namespace std;
template <typename T1, typename T2>
class MyClass {
private:
T1 t1;
T2 t2;
public:
MyClass( T1 t1 = T1(), T2 t2 = T2() ) { }
void printSizeOfDataTypes() {
cout << "\nSize of t1 is " << sizeof ( t1 ) << " bytes." << endl;
cout << "\nSize of t2 is " << sizeof ( t2 ) << " bytes." << endl;
}
};
int main ( ) {
//Until C++ 14
MyClass<int, double> obj1;
obj1.printSizeOfDataTypes( );
//New syntax in C++ 17
MyClass obj2( 1, 10.56 );
return 0;
}
可以使用以下命令编译前面的代码并查看输出:
g++-7 main.cpp -std=c++ 17
./a.out
程序的输出如下:
Size of t1 is 4 bytes.
Size of t2 is 8 bytes.
就像 C++ 中的内联函数一样,现在可以使用内联变量定义。这对于初始化静态变量非常有用,如下面的示例代码所示:
#include <iostream>
using namespace std;
class MyClass {
private:
static inline int count = 0;
public:
MyClass() {
++ count;
}
public:
void printCount( ) {
cout << "\nCount value is " << count << endl;
}
};
int main ( ) {
MyClass obj;
obj.printCount( ) ;
return 0;
}
可以使用以下命令编译前面的代码并查看输出:
g++-7 main.cpp -std=c++ 17
./a.out
前面代码的输出如下:
Count value is 1
在本章中,您将了解 C++ 17 中引入的有趣的新特性。您学习了超级简单的 C++ 17 嵌套命名空间语法。您还学习了使用支撑初始化列表的数据类型检测以及 C++ 17 标准中的新规则。
您还注意到static_assert
可以在不断言失败消息的情况下完成。此外,使用std::invoke()
,您现在可以调用全局函数、函数指针、成员函数和静态类成员函数。而且,使用结构化绑定,您现在可以用一个返回值初始化多个变量。
您还了解到if
和switch
语句可以在if
条件和switch
语句之前有一个局部变量。您已经学习了类模板的自动类型检测。最后,你使用了inline
变量。
还有更多的 C++ 17 特性,但本章试图涵盖大多数开发人员可能需要的最有用的特性。在下一章中,您将学习标准模板库。