C++ 2.0新特性——关键字
一、nullptr
nullptr不具备整型类型,实际类型为nullptr_t,可以隐式转化为所有的裸指针类型
void f(int);
void f(bool);
void f(void*)
f(0):调用f(int),而不是f(void*);
f(null):可能编译无法通过,一般调用f(int)。不会调用f(void*)。
f(nullptr):调用f(void*)
1、相较于0或NULL,优先使用nullptr
2、避免了在整型和指针之间重载,很好地区分了整型0和指针类型nullptr
二、别名声明——using
优先使用using而非typedef:
1、typedef不支持模板化,而别名声明(using)支持。
template<typename T>
using MyAllocList = list<T, MyAlloc<T>>;
MyAllocList<Widget> lw;
template<typename T>
struct MyAllocList
{
typedef list<T, MyAlloc<T>> type;
};
MyAllocList<Widget>::type lw;
2、可以让人免写"::type"后缀,并且在模板内,对于内嵌typedef的引用经常加上typename前缀
class Widget{
typename MyAllocList<T>::type list;
};
MyAllocList::type list代表一个依赖模板类型形参的类型,称为依赖类型,需要加typename前缀,使用别名声明,则MyAllocList list即可。
三、限定作用域枚举enum class
优先使用限定作用域枚举类型,而非不限作用域的枚举类型。
优点:
1、限定作用域的枚举类型仅在枚举类型内部可见。只能通过强制类型转换至其他类型。好处:
(1)、限定作用域的枚举类型带来的名字空间污染降低。
enum Color { black, white, red };
auto white = false;//错误。white和Color的作用域相同,已经被声明过了
enum class Color { black, white, red };
auto white = false;//正确,范围内无white
Color c = white;//错误,范围内无white
Color c = Color::white;//正确
(2)、enum class的枚举量是强类型的,不存在隐式转换类型。只能通过强制类型转换static_cast。而enum允许隐式转换的。
(3)、可以进行前置声明
enum Color;//错误
enum class Color;//正确
2、限定作用域的枚举类型和不限范围的枚举类型都支持底层类型指定。enum class底层类型分别为int,enum没有默认的底层类型。
enum class Status;//底层类型为int
enum class Status: double//指定类型为double
3、enum class总是可以进行前置声明,而enum只有在指定了默认底层类型的前提下进行前置声明。
四、删除函数=delete
优先使用删除函数,而非private未定义函数。删除系统自动生成的默认构造函数、拷贝构造函数等。任何函数都可以删除,包括非成员函数和模板具现。
五、改写函数override
改写函数的条件:
1、基类中的函数必须是虚函数
2、基类和派生类中的函数名必须完全相同
3、基类和派生类中的函数形参类型必须完全相同
4、基类和派生类中的函数常量性必须完全相同
5、基类和派生类中的函数返回值和异常规格必须兼容
6、基类和派生类中的函数引用饰词必须完全相同
class Widget{
void do() &;//仅在*this为左值时调用
void do() &&;//仅在*this为右值时调用
};
改写函数时,在函数后加override,能保证在编译时检查改写的要求是否满足。
六、常量迭代器——const_iterator
任何时候只要需要一个迭代器,其指代的内容不需要修改,则使用const_iterator。此时容器中的cbegin() cend()都返回const_iterator
vector<int> values;
auto it = find(values.cbegin(), values.cend(), 1);
...
values.insert(it, 2);
注:C++11中仅添加了非成员函数版本的begin和end,而没有添加cbegin(),cend(),rbegin() ,rend()、crbegin()、crend()。C++14都添加了。
在设计通用代码时,优先使用非成员函数版本的begin()、end()等,而非成员函数。
七、函数异常——noexcept
只要函数不会发射异常,就在函数后加上noexcept声明。
1、noexcept声明是函数接口的组成部分,这意味着调用方式可能会对它有依赖。当明知一个函数不会发射异常而不加上noexcept声明,这就是接口规格缺陷,类似于函数加const;同时让编译器生成更好的目标代码。
加上noexcept声明,使得优化器不需要在异常传出函数的前提下,将执行期栈保持在开解状态;也不需要在异常溢出函数的前提下,保证所有其中的对象以其被构造顺序的逆序完成析构。有更多机会得到优化
2、noexcept性质对于移动操作、swap、函数释放函数和函数析构最有价值。
3、大部分函数都是异常中立的:自身不抛出异常,但他们的调用函数可能发射异常,不具备noexcept性质。
4、一般把nonexcept的声明保留给那些带有“宽松契约”的函数上。
**宽松契约:**无前置条件,调用此函数无需关注程序状态,传入的实参也无限制。
八、常量——constexpr
只要有可能使用constexpr,就使用它。
1、所有constexpr对象都是const对象,并非所有的const对象都是constexpr对象,constexpr由编译期已知的值完成初始化。
int s = 0; //非constexpr变量
constexpr auto arraySize1 = s;//报错,s的值在编译期未知。会显示s的值不可用做常量。
array<int, s> data1;//错误,同上
constexpr auto arraySize2 = 10;//正确,10是个编译期常量
array<int, arraySize2> data2;//正确
换作const:
int s = 0;
const auto arraySize = s;//正确,arraySize是s的一个const副本
array<int, arraySize> data;//错误,arraySize的值非编译期未知,会显示arraySize的值不可用做常量。
2、constexpr函数在调用时,若传入的实参值是编译期已知的,则会产生编译期结果;若传入的一个或多个在编译期未知,则在运行期执行结果计算,和普通函数无异。
constexpr int pow(int base,int exp) noexcept{}
constexpr并不表明pow要返回一个const值,而是表明若base和exp均为编译期常量,则pow返回结果可以当做一个编译期常量使用;若有一个不是编译期常量,则pow返回的结果就是在执行期计算
3、constexpr对象或者函数能够应用在作用域更广的语境中。
九、const函数的线程安全性——互斥量和原子操作
在多线程中,线程中的一个或多个可能企图修改数据成员,导致有多个不同的线程在没有同步的情况下读写同一块内存,造成“数据竞争”。此时可以使用互斥量mutex加锁
mutable mutex m;
void f() const{
lock_guard<mutex> g(m);//加互斥量,加锁
..
}//解锁
对于单个要求同步的变量或内存区域,使用atomic就足够了;但是若有两个或多个变量或内存区域作为一整个单位进行操作,就要使用互斥量。
mutable atomic<unsigned> count{0};
void f() const noexcept{
++count;//带原子性自增
..
}