1.auto

auto是旧关键字,在C++11之前,auto用来声明自动变量,表明变量存储在栈,很少使用。在C++11中被赋予了新的含义和作用,用于类型推断。

auto关键字主要有两种用途:一是在变量定义时根据初始化表达式自动推断该变量的类型,二是在声明或定义函数时作为函数返回值的占位符,此时需要与关键字decltype连用。

1.1用法示例

(1)auto用于推断变量类型示例。

auto i = 42;        //i is an int

auto l = 42LL;      //l is an long long

auto p = new foo(); //p is a foo*

(2)声明或定义函数时作为函数返回值的占位符。

auto不能用来声明函数的返回值。但如果函数有一个尾随的返回类型时,auto是可以出现在函数声明中返回值位置。这种情况下,auto并不是告诉编译器去推断返回类型,而是指引编译器去函数的末端寻找返回值类型。在下面这个例子中,函数返回值类型是operator+操作符作用在T、U类型变量上的返回值类型。

template<class T, class U> auto add(T t, U u) -> decltype(t + u){
    return t + u;
}

2.decltype

decltype与auto关键字一样,用于进行编译时类型推导,不过它与auto还是有一些区别的。decltype的类型推导并不是像auto一样是从变量声明的初始化表达式获得变量的类型,而是总是以一个普通表达式作为参数,返回该表达式的类型,而且decltype并不会对表达式进行求值[2]。

2.1decltype推导规则

(1)如果e是一个变量或者类成员访问表达式,假设e的类型是T,那么的decltype(e)为T,decltype((e))为T&。

(2)如果e是一个解引用操作,那么decltype(e)和decltype((e))均为T&。

(3)否则decltype(e)与decltype((e))均为T。

2.2用法示例

(1)推导出表达式类型。

struct A { double x; };
const A* a = new A{0};

//第一种情况
decltype(a->x) y;       // type of y is double
decltype((a->x)) z = y; // type of z is const double&,因为a一个常量对象指针

//第二种情况
int* aa=new int;
decltype(*aa) y=*aa;    //type of y is int&,解引用操作

//第三种情况
decltype(5) y;          //type of y is int
decltype((5)) y;        //type of y is int
const int&& RvalRef() { return 1; }
decltype ((RvalRef())) var = 1;  //type of var is const int&&

(2)与using/typedef合用,用于定义类型。

using size_t = decltype(sizeof(0));//sizeof(a)的返回值为size_t类型
using ptrdiff_t = decltype((int*)0 - (int*)0);
using nullptr_t = decltype(nullptr);

vector<int >vec;
typedef decltype(vec.begin()) vectype;
for (vectype i = vec.begin; i != vec.end(); i++){
        //...
}

显而易见,与auto一样,也提高了代码的可读性。

(3)泛型编程中结合auto,用于追踪函数的返回值类型,这也是decltype的最大用途。

template <typename _Tx, typename _Ty>
auto multiply(_Tx x, _Ty y)->decltype(x*y)
{
    return x*y;
}

3.nullptr

以前都是用0来表示空指针的,但由于0可以被隐式类型转换为整形,这就会存在一些问题。关键字nullptr是std::nullptr_t类型的值,用来指代空指针。nullptr和任何指针类型以及类成员指针类型的空值之间可以发生隐式类型转换,同样也可以隐式转换为bool型(取值为false)。但是不存在到整形的隐式类型转换[3]。

int* p1 = NULL;
//或
int* p2 = nullptr;

4.constexpr

constexpr再C++11中用于申明常量表达式(const expression)。常量表达式是指值不会改变并且在编译过程中就得到计算结果的表达式[4]。

const int i=3;    //i是一个常量变量

const int j=i+1;  //j是一个常变量,i+1是一个常量表达式

int k=23;         //k的值可以改变,从而不是一个常变量

const int m=f();  //m不是常变量,m的值只有在运行时才会获取。

一般来说,若果一旦认定变量是一个常量表达式,那就把它声明为constexpr类型。

必须明确一点,在constexpr声明中,如果定义了一个指针,限定符号constexpr仅仅对指针有效,与指针所指对象无关。

const int *p=nullptr;       //p是一个指向整型常量的指针(pointer to const)
constexpr int *p1=nullptr;  //p1是一个常量指针(const pointer)

5.noexcept

在C++11标准之前,C++在函数声明中有exception specification(异常声明)的功能,用来指定函数可能抛出的异常类型[5]。

voidFunc0() throw(runtime_error);
voidFunc1() throw();
voidFunc2();

函数Func0可能抛出runtime_error类型的异常;函数Func1不会抛出任何异常;函数Func2没有异常说明,则该函数可以抛出任何类型的异常。

如果函数抛出了没有在异常说明中列出的异常,则编译器会调用标准库函数unexpected。默认情况下,unexpected函数会调用terminate函数终止程序。

这种异常声明的功能很少使用,因此在C++11中被弃用(实际仍可使用)。C++11引入noexcept,具有两层含义,一个是修饰符,而是操作符。具体用法如下。

(1)修饰符示例。

voidFunc3() noexcept;

noexcept的功能相当于上面的throw(),表示函数不会抛出异常。如果noexcept修饰的函数抛出了异常,编译器可以选择直接调用std::terminate()终止程序运行。noexcept比throw()效率高一些。

voidFunc4() noexcept(常量表达式);

如果常量表达式的结果为true,表示该函数不会抛出异常,反之则有可能抛出异常。不带常量表达式的noexcept相当于noexcept(true)。

(2)操作符示例。

上面noexcept的用法是其作为修饰符时的用法,实际上noexcept还可以作为操作符,常用于模板中。

template <typename T> void func5() noexcept( noexcept(T()) ) {}

第2个noexcept就是一个操作符,如果其参数是一个有可能抛出异常的表达式,则返回值为false,那么func5有可能会抛出异常,否则返回值为true,func5为noexcept(true),不会抛出异常。

这样函数是否会抛出异常,可以由表达式进行推导,使得c++11更好的支持泛型编程。

6.final和override

2012 年 3 月 22 日,GCC 4.7.0 正式发布。从这个版本开始,GCC 增加了许多新的 C++ 11 的特性。今天我们要介绍的是其中的一个特性:显式地使用 final和override关键字[6]。

6.1final

(1)final用于修饰类。

final修饰类,可用于申明终结类。从此C++终于有申明终结类的关键字了。

struct B1 final { };

struct D1 : B1 { }; // 错误!不能从 final 类继承!

上面的代码是错误的,因为 D1 试图继承 B1,而 B1 被 final声明为终结类,类似于Java的关键字的作用。

(2)final用于修饰虚函数。

final用于修饰虚函数,表明子类不能重写该虚函数,为”终结虚函数“。例如:

struct B2
{
    virtual void f() final {} // final 函数
};

struct D2 : B2
{
    virtual void f() {}
};

这段代码会出错,因为D2::f重写了B2::f,但是B2::f却被声明为 final 。

6.2override

假如我们继承基类的虚函数,在重写虚函数时写错了,参数类型不对或个数不对,但是编译没问题,造成了对基类同名函数的隐藏,运行时候和设计的不一样,override就是辅助检查是否正真重写了继承的虚函数。例如:

struct B3
{
    virtual void f() {}
};

struct D3 : B3
{
    void f() {}
};

开发 D3 的程序员真的想重写B3::f函数吗?还是说,他只是不小心写了个与父类同名的函数,却在不经意间导致了隐藏?为了避免这种错误,C++ 11 引入了override关键字。于是,我们会发现,下面的一段代码是会出错的:

struct B4
{
    virtual void g(int) {}
};

struct D4 : B4
{
    virtual void g(int) override {} // OK
    virtual void g(double) override {} // Error
};

多亏了override关键字,我们可以让编译器帮我们检测到这个很难发现的程序错误。这段代码的错误在于,override关键字表明,g(double)虽然想要进行override的操作,但实际父类并没有这么个函数。在实际开发中,建议大家重写继承而来的虚函数时,加上关键字virtual表明当前函数式虚函数,C++编译器的”放纵“降低了代码的可读性。

值得注意的是,这些并不是一些语法糖,而是能确确实实地避免很多程序错误,并且暗示编译器可以作出一些优化。调用标记了final的virtual函数,例如上面的B2::f,GNU C++ 前端会识别出,这个函数不能被覆盖,因此会将其从类的虚表中删除。而标记为final的类,例如上面的 B1,编译器则根本不会生成虚表。这样的代码显然更有效率。

7.sizeof…运算符

sizeof…运算符的作用是获取C++11中可变参数模板中参数包中元素个数。类似sizeof,sizeof…返回一个常量表达式,而且不会对模板的实参求值[7]。例如:

template<typename... Args> void g(Args... args){
    cout<<sizeof...(Args)<<endl;  //类型参数的数目
    cout<<sizeof...(args)<<endl;  //函数参数的数目
}

8.default和delete[8]

8.1default

我们知道,C++98和C++03编译器在类中会隐式地产生四个函数:默认构造函数、拷贝构造函数、析构函数和赋值运算符函数,它们被称为特殊成员函数。在 C++11 中,被称为 “特殊成员函数” 的还有两个:移动构造函数和移动赋值运算符函数。如果用户申明了上面六种函数,编译器则不会隐式产生。C++引入的default关键字,可显示地、强制地要求编译器为我们生成默认版本。

class DataOnly{
public:
    DataOnly()=default;               //default constructor
    ~DataOnly()=default;              //destructor

    DataOnly(const DataOnly& rhs)=default;    //copy constructor
    DataOnly& operator=(const DataOnly & rhs)=default;  //copy assignment operator

    DataOnly(const DataOnly && rhs)=default;  //C++11,move constructor
    DataOnly& operator=(DataOnly && rhs)=default;  //C++11,move assignment operator
};

上面的代码,就可以让编译器生成上面六个函数的默认版本。

8.2delete

delete关键在C++11之前是对象释放运算符,但在C++11中,被赋予了新的功能,主要有如下几种作用:

(1)禁止编译器生成上面六种函数的默认版本。

class DataOnly{
public:
    DataOnly()=delete;               //default constructor
    ~DataOnly()=delete;              //destructor

    DataOnly(const DataOnly& rhs)=delete;    //copy constructor
    DataOnly& operator=(const DataOnly & rhs)=delete;  //copy assignment operator

    DataOnly(const DataOnly && rhs)=delete;  //C++11,move constructor
    DataOnly& operator=(DataOnly && rhs)=delete;  //C++11,move assignment operator
};

(2)C++11 中,delete 关键字可用于任何函数,不仅仅局限于类成员函数。在函数重载中,可用delete来滤掉一些函数的形参类型,如下:

bool isLucky(int number);        // original function
bool isLucky(char) = delete;     // reject chars
bool isLucky(bool) = delete;     // reject bools
bool isLucky(double) = delete;   // reject doubles and floats

这样在调用 isLucky 函数时,如果参数类型不对,则会出现错误提示

if (isLucky('a'))...       // error! call to deleted function
if (isLucky(true))...      // error!
if (isLucky(3.5))...       // error!

(3)在模板特例化中,也可以用 delete 来过滤一些特定的形参类型。例如,Widget 类中声明了一个函数模板,当进行模板特化时,要求禁止参数为 void* 的函数调用。

class Widget {
public:
    template<typename T> void processPointer(T* ptr){}
};
template<> void Widget::processPointer<void>(void*)=delete; //deleted function template

参考文献

[1]【C++11新特性】auto关键字

[2]C++11特性:decltype关键字

[3]C++开发者都应该使用的10个C++11特性

[4]constexpr与常量表达式(c++11标准)

[5][了解C++11(五)—— noexcept]{http://www.xuebuyuan.com/2069091.html}

[6]C++11 新特性:显式 override 和 final

[7]C++ primer中文版第五版:619-619

[8]C++11 之 delete 和 default

C++11——引入的新关键字的更多相关文章

  1. C++11特性:auto关键字

    前言 本文的内容已经不新鲜了.关于auto,翻来覆去被人知道的都是这些东西,本文并没有提出新颖的auto用法. 本人原是痛恨博客一篇篇都是copy而来缺乏新意的探索,当然,本文不是copy而来,但发布 ...

  2. 一起学习c++11——c++11中的新语法

    c++11新语法1: auto关键字 c++11 添加的最有用的一个特性应该就是auto关键字. 不知道大家有没有写过这样的代码: std::map<std::string, std::vect ...

  3. c++builder XE7 C++11 C++0x 新语法

    Non-static data member initializers 非静态成员变量初始化变得简单,这个XE7 64位编译成功,32位还是不支持 As a simple example, struc ...

  4. Mysql8.0.11简介,新特性

    MySQL 8.0 正式版 8.0.11 已发布,官方表示 MySQL 8 要比 MySQL 5.7 快 2 倍,还带来了大量的改进和更快的性能! 注意:从 MySQL 5.7 升级到 MySQL 8 ...

  5. kafka 幂等生产者及事务(kafka0.11之后版本新特性)

    1. 幂等性设计1.1 引入目的生产者重复生产消息.生产者进行retry会产生重试时,会重复产生消息.有了幂等性之后,在进行retry重试时,只会生成一个消息. 1.2 幂等性实现1.2.1 PID ...

  6. C++11 override 和 final 关键字

    C++11之前,一直没有继承控制关键字.禁用一个类的进一步衍生是可能的但也很棘手.为避免用户在派生类中重载一个虚函数,你不得不向后考虑. C++ 11添加了两个继承控制关键字:override和fin ...

  7. 利用ABAP 740的新关键字REDUCE完成一个实际工作任务

    ABAP 740从2013年发布至今已经过去很长的时间了,下面这张图来自SAP社区博客: ABAP News for Release 7.40 – What is ABAP 7.40? 图中的ABAP ...

  8. C# 9.0中引入的新特性init和record的使用思考

    写在前面 .NET 5.0已经发布,C# 9.0也为我们带来了许多新特性,其中最让我印象深刻的就是init和record type,很多文章已经把这两个新特性讨论的差不多了,本文不再详细讨论,而是通过 ...

  9. (数据科学学习手札139)geopandas 0.11版本重要新特性一览

    本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 大家好我是费老师,就在几天前,geopandas ...

随机推荐

  1. youtube高清视频下载方法

    youtube下载方法有多种, 但都不支持1080P以上的高清下载, 今天找到一种支持1080P的, 记录一下 步骤1: 百度搜: Dooseen tubedown 下载该软件, 并安装, 一直下一步 ...

  2. linux常用的查看设备的命令

    系统 # uname -a # 查看内核/操作系统/CPU信息  # head -n 1 /etc/issue # 查看操作系统版本 # cat /proc/cpuinfo # 查看CPU信息  # ...

  3. 基于Mininet测量路径的损耗率

    基于Mininet测量路径的损耗率 控制器采用POX,基于OVS仿真 Mininet脚本 创建Node mininet.node Node 创建链路连接 mininet.link TCLink 设置i ...

  4. nordic-mesh中应用的代码实现

    nordic-mesh中应用的代码实现 Nordic-Mesh遵循SIG-Mesh-Profile中的mesh定义,实现了element.model等概念. 一个应用中包含一个或多个element,e ...

  5. JavaScript设计模式-----命令模式的简单应用

    命令模式是最简单和优雅的模式之一,命令模式中的命令(command)是指一个执行某些特定事情的指令. 应用场景:有时候需要向某些对象发送请求,但是并不知道请求的接受者是谁,也不知道被请求的操作是什么, ...

  6. ES6的新特性(3)——变量的解构赋值

    变量的解构赋值 数组的解构赋值 基本用法 ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring). let a = 1; let b = 2; le ...

  7. 《Linux内核与分析》第八周

    by 20135130王川东 一.进程切换关键代码switch-to分析     1.进程调度与进程调度时机分析 1)不同类型的进程有不同的调度要求 分类:I/0-bound:频繁的进行I/o 通常会 ...

  8. Java中的抽象类abstract

    abstract定义抽象类 abstract定义抽象方法,只需要声明,不需要实现 包含抽象方法的类是抽象类 抽象类中可以包含抽象方法,也可以包含普通方法 抽象类不能直接创建,可以定义父类引用变量指向子 ...

  9. 2018软工实践—Alpha冲刺(3)

    队名 火箭少男100 组长博客 林燊大哥 作业博客 Alpha 冲鸭鸭鸭! 成员冲刺阶段情况 林燊(组长) 过去两天完成了哪些任务 协调各成员之间的工作 协助后端界面的开发 搭建项目运行的服务器环境 ...

  10. mininet实验 脚本实现控制交换机行为

    写在前面 本文参考 通过这个实验,我学习到了另一种下流表的方式. 下流表有两种方式(我目前了解): 通过controller下发. 通过OvS提供的API直接向OvS交换机下流表. 本实验脚本已经把相 ...