C/C++tips3(c++11及以后)

吐槽一下,经典的c++98经过c++11和c++14的升级后,似乎一下变成了另一种编程语言了。c++11和c++14引入了大量的新特性,使我感觉c++的复杂程度一下上升了数倍。
这里的很多主题都非常复杂,我仅仅把我自己的理解大致记录下来,具体内容还是要去看看c++主题演讲

cv-qualified

在一些资料中经常看到cv-qualified type之类的词语,这里的cv就是const & volatile,即不变和易变修饰符,cv-qualified type就是以const或者volatile修饰的类型。
在使用模版函数或者auto时,根据参数的类型,编译器的类型推导会自动地推导出cv-qualified type。
例如:

// 1\. 模版函数

template
void func(T& t) {…}
const int a = 1;
func(a);    // 模版函数参数的类型成为const int&

// 2\. auto
const int a = 3;
auto& var = a;    // var的类型成为const int&

但是,如果模版函数或者auto不是引用或者指针,那么得到的类型是去除了cv-qualified的类型。因为此时新的标识符有了独立的内存空间,对于原标识符内存空间的修饰当然不应该继续生效了。

左值和右值

C++规定:凡是能够通过取地址运算符取得变量在内存中地址的,就是左值,否则就是右值。可以这样理解:左值是存在于内存中的变量(不论是堆内存还是栈内存),而右值是仅存在于寄存器中的临时变量。

decltype

decltype可以推断一个标识符的类型,这个类型是编译时推导出的。
由于lambda表达式的类型是无名的,因此如果想要获得lambda表达式的类型,只能使用decltype(lambdaExpr)。例如:

auto f = [](int a, int b) -> int
{
    return a * b;
};

decltype(f) g = f; // lambda 的类型是独有且无名的
i = f(2, 2);
j = g(3, 3);

using和typedef

在C++11中,鼓励使用using来替代原来的typedef定义类型别名。using强制将类型名称放到等号左边,而将类型放到等号右边,提高了可读性;此外,using还支持泛型,而typedef不支持泛型。
举一个例子:

typedef int (*myFunc)(int, char*);
using myFunc = int (*)(int, char*);

类型推断(type deduction)

scott mayer的演讲阐明了一些非常容易被忽视或者混淆的基本概念,理解这些基础是理解C++11和C++14新增内容的必要知识。
https://www.youtube.com/watch?v=wQxj20X-tIU

右值引用 & 移动语义

右值引用和移动语义也是在C++11新增的词,用于解决对象在右值赋值时会不必要地进行额外操作的问题。此外,同样利用C++0x引用的T&&右值引用这种写法,也可以实现所谓的“通用引用Universal Reference”,从而实现完美转发。
C++从一开始就规定了引用的语义,不过却没有提到“左值”或者“右值”引用,事实上,左值引用正是我们一直以来在C++中使用的引用,过去的C++标准没有规定右值引用,因此过去一直无法使用右值引用。C++0x中规定:右值引用能够通过两个引用符号表示,如果一个函数的参数类型是T&&,那么这个函数就会匹配右值引用参数。使用右值引用时,我们可以确定右值引用对象在函数被调用完成后就会被丢弃,因此可以直接将右值引用对象资源“移动”到this对象中而不用担心这样做会导致右值引用对象的状态遭到破坏(因为之后这个对象就用不到了,会被立即销毁)。
如上所述,使用右值引用时,能够确保右值引用对象会在随后被销毁,因此可以将右值引用对象的资源直接“移动”到当前对象中,“移动”就是指将资源浅拷贝到当前对象,然后将右值引用对象中的资源引用置空。这样,原本由右值引用持有的资源就移动到了当前对象中。这就是移动语义。
C++0x同时在标准库中新增了std::move,move将参数转为右值引用并返回,而forward根据参数是左值还是右值返回对应的引用。

通用引用 & 完美转发

通用引用其实就是右值引用,但是scott mayer认为右值引用不准确,因为有时候“右值引用”引用的其实不是一个右值,因此他坚持使用“通用引用”而不是“右值引用”。
以下列举通用引用的例子:

// 1\. T由编译器进行类型推倒
template 
void function(T&& t);

// 2\. 使用auto,auto的推导规则其实和范型方法一样,因此也可以被归为上面一类
auto&& t;

完美转发(perfect forwarding)问题是指函数模板在向其他函数传递参数时该如何保留该参数的左右值属性的问题。
也就是说函数模板在向其他函数传递自身形参时,如果相应实参是左值,它就应该被转发为左值;同样如果相应实参是右值,它就应该被转发为右值。
这样做是为了让其他函数能够针对转发而来的参数的左右值属性进行不同处理。
参考上面提到的通用引用的例子1,此时不论T&&被推导为左值引用还是右值引用,实际上的t都会变成左值引用,因为在函数内的参数引用有了名字,能够被引用了,从而不符合右值的定义。但是,编译器会记住t实际上是个右值,如果希望获取t原本的引用类型,就需要使用新增的std::forward方法来获取引用的实际类型。

lambda

auto

auto是自动类型推导,可以根据表达式的返回自动推导出变量的类型。可以使用const & volatile以及引用&指针来修饰auto类型标识。

std::initializer_list

c++11标准库中新增了一个重要的类型定义:

template< class T >
class initializer_list;

一个initializer_list是一个轻量级的代理对象,可以通过它访问一组类型为T的数组。
在以下情况下,initializer_list会被自动创建:

// class MyClass有一个接受std::initializer_list的构造函数
MyClass a({1, 2, 3});
MyClass a = {1, 2, 3};
for (auto& a : {1, 2, 3}) {...}

你可能感兴趣的:(C/C++tips3(c++11及以后))