Skip to content

Latest commit

 

History

History
480 lines (341 loc) · 12.6 KB

File metadata and controls

480 lines (341 loc) · 12.6 KB

一、C++ 17 特性

在本章中,您将学习以下概念:

  • C++ 17 背景
  • 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 有什么新功能?

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 中有哪些功能被弃用或删除了?

现在,C++ 17 中删除了以下功能:

  • register关键字在 C++ 11 中被弃用,在 C++ 17 中被删除
  • bool++ 运算符在 C++ 98 中被弃用,在 C++ 17 中被移除
  • 动态异常规范在 C++ 11 中被弃用,在 C++ 17 中被删除

C++ 17 中的关键特性

让我们在以下几节中逐一探讨以下 C++ 17 关键特性:

  • 更简单的嵌套命名空间
  • 从支撑初始化列表中检测类型的新规则
  • 简化static_assert
  • std::invoke
  • 结构化绑定
  • ifswitch局部变量
  • 类模板的模板类型自动检测
  • 内联变量

更简单的嵌套命名空间语法

直到 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++ 社区。

标准::invoke()方法

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 nameint age变量。这些都是由 C++ 编译器自动推导出来的stringint,这使得 C++ 的语法就像任何现代编程语言一样,不会失去其性能和系统编程的好处。

可以使用以下命令编译前面的代码并查看输出:

g++-7 main.cpp -std=c++ 17
./a.out

前面程序的输出如下:

Name of the student is Sriram
Age of the student is 10

If 和 Switch 局部作用域变量

有一个有趣的新特性,允许你声明一个局部变量绑定到ifswitch语句的代码块。在ifswitch语句中使用的变量的范围将超出各自块的范围。通过一个简单易懂的例子可以更好地理解,如下所示:

#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(),您现在可以调用全局函数、函数指针、成员函数和静态类成员函数。而且,使用结构化绑定,您现在可以用一个返回值初始化多个变量。

您还了解到ifswitch语句可以在if条件和switch语句之前有一个局部变量。您已经学习了类模板的自动类型检测。最后,你使用了inline变量。

还有更多的 C++ 17 特性,但本章试图涵盖大多数开发人员可能需要的最有用的特性。在下一章中,您将学习标准模板库。