C++ 11
1. __cpluscplus
在C++11中, __cpluscplus
宏定义的值不同于之前的值.
2. lvalue, rvalue, xvalue
- 左值(lvalue): 可取地址, 有名字的对象
- 右值(rvalue): C98中定义为临时变量值
- 纯右值(pvalue): C++11引入的新语义, 包括字面值和不具名的临时对象(例如: 1, false, ++i, a+b, a==b等)
- 将亡值(xvalue): C++11随引入右值引用而引入的新语义, 一个生命周期即将结束的对象都可以称为将亡值
C++11之前有引用(reference), C++11将引用拆分为两种:
- 左值引用(lvalue reference): 与之前的引用定义相同, 为具名变量值的别名(alias)
- 右值引用(rvalue reference): 右值的引用, 右值不存在名称, 所以只能通过其引用查找(匿名变量值的别名)
int a1 = 1; |
左值不能直接绑定到右值引用上, 必须使用std::move()将左值转换为右值
int a = 1; |
以下是引用绑定的规则:
reference type | non-const lvalue | const lvalue | non-const rvalue | const rvalue |
---|---|---|---|---|
non-const lvalue reference | Y | N | Y | Y |
const lvalue reference | Y | Y | Y | Y |
non-const rvalue reference | N | N | Y | N |
const rvalue reference | N | N | Y | Y |
3. decltype
用于查看实例或表达式的类型, decltype不会计算表达式的结果, 只会返回表达式结果的类型.
- 如果e是一个没有括号的标记符表达式(id-expression)或类成员访问式(class member access expression), 则decltype(e)就是e所命名的的类型(id-expression: 除去关键字和字面量等编译器所需的标记外, 程序员定义的标记)
int a = 1;
int arr[5] = {0};
int* p = arr;
struct S{double d;}s;
decltype(p) x; // x的类型为int*
decltype(a) y; // y的类型为int
decltype(arr) z; // z的类型为int
decltype(s.d) m; // m的类型为double - 否则, 假设e的类型为T, 若e为将亡值, 则decltype(e)为T&&
int&& Func();
decltype(Func()) x = 1; // x的类型为rvalue reference(int&&) - 否则, 假设e的类型为T, 若e为左值, 则decltype(e)为T&
int a = 1;
decltype((a)) x = a; // x的类型为non-const lvalue reference(int&) - 否则, 假设e的类型为T, 则decltype(e)为T
int i = 1;
bool Func();
decltype(1) x = 1; // 1为pvalue, 所以x的类型为int
decltype(i++) y = 1; // i++为pvalue, 所以y的类型为int
decltype(Func()) z = true; // Func()为pvalue, 所以z的类型为bool
4. auto
功能上与decltype相同, 通过表达式来推断数据类型.
4.1 auto与decltype的不同之处
- 基本类型
int i = 1, &l = i;
auto x = 1; // x的类型为int
auto y = i; // y的类型为int - const, volatile和reference都不能自动推导, 必须显式声明
int i = 1, &l = i;
auto t1 = l; // t1的类型为int
auto& t2 = i; // t2的类型为i的lvalue reference
auto p1 = &i; // p1的类型为int
auto* p2 = &i; // p2的类型为pointer to i
auto&& r1 = i; // r1的类型为i的lvalue reference
auto&& r2 = std::move(i); // r2为i的rvalue reference
// auto m = 1, n = 2.0; // error, m和n类型不同 - 顶层const和底层const
const int i = 1;
auto a = i; // a的类型为int, 摘除了顶端const
const auto b = i; // b的类型为const int, 需要指出顶层const
auto* c = &i; // c的类型为const int*, 顶层const不被摘除 - 数组会自动推导为指针
int i[10];
auto p1 = i; // p1的类型为int*
auto& p2 = i; // p2的类型为int[10]
4.2 auto的主要用途
- 用于代替冗长复杂的变量声明
std::vector<std::string> vs;
// 使用auto代替std::vector<std::string>::iterator
for (auto i = vs.begin(); i != vs.end(); i++)
{
//..
} - 定义模板函数时,用于声明依赖模板参数的变量类型
template <typename T1, typename T2>
void Multiply(T1 x, T2 y)
{
auto v = x * y; // 如果不用auto, 很难定义v的类型
} - 模板函数依赖于模板参数的返回值
// auto在这里只是一个占位符, 真正的返回值类型定义在decltype
template <typename T1, typename T2>
auto multiply(T1 x, T2 y) -> decltype(x * y)
{
return x * y;
}
/*
* 之所以不定义为:
* template <typename T1, typename T2>
* decltype(x * y) multiply(T1 x, T2 y)
* {
* return x * y;
* }
* 是因为decltype中的x和y还没定义, 会报编译错误
*/
5. Control of member function
C++有四种特殊的成员函数(member function):
- default constructor(默认构造函数)
- copy constructor(拷贝构造函数)
- copy assignment operator(拷贝赋值运算符)
- destructor(析构函数)
如果没有显式声明这些函数, 编译器会生成默认的对应成员函数. C++11通过添加两个specifier来控制这四个成员函数:
- =default
- =delete
template<class T> |
6. Uniform initialization
6.1 诞生原因
- 在C++03中, 只有类成员变量才能用Member initializer lists(成员初始化列表)进行初始化, C++11扩大了初始化列表的适用范围, 并支持列表初始化方法:
int v1 = 0;
int v2(1); // ()和=本质上没有区别
int v3{3}; // list initializer, 列表初始化
int v4 = {4}; // 同上 - 对于某些特殊类型, 不能使用某种方式来初始化
- C++11引进了atomic原子类型, 这种类型的变量无法使用=来初始化
std::atomic<int> v1{1};
std::atomic<double> v2(2.0);
// std::atomic<float> v3 = 3.0f; // error - 对于类中的非静态成员变量, 设置默认值不能使用()
class C1 {
private:
int v1 = 1;
int v2{2};
// int v3(3); // error
}; - 调用自定义类的无参数构造函数时, 会被编译器解释为函数声明
C1 c1(); // empty parentheses interpreted as a function declaration
// c1.v = 2; // error: expression is not assignable
C1 c2{};
c2.v = 2; - 初始化容器时会产生歧义
std::vector<int> v1{10}; // the size of v1 is 1
std::vector<int> v2(10); // the size of v2 is 10 - 如果存在类型收窄的情况, 则编译器不会报错. 而列表初始化可以避免这个问题
double d = 3.1415926;
int i1(d); // i1为3
int i2 = d; // i2为3
// int i3{d}; // error, type 'double' cannot be narrowed to 'int'
// int i4 = {d};// error, type 'double' cannot be narrowed to 'int'
由此可见, 列表初始化**{}**成为适用面最广的初始化方法, 并且在某些方面优于=和(), 从而被称为统一初始化(Uniform initialization)
6.2 存在的问题
列表初始化在绝大多数情况下都能很好的运行, 但当遇到initializer_list时会产生很多问题: 当使用{}调用构造函数时, 会优先调用initializer_list为参数类型的构造函数(无参数构造函数除外)
class C1 |
7. Initializer lists
7.1 诞生原因
C++03继承了C语言的initializer-list特性, 只含有POD(Plain Old Data)的class和struct都可以通过initializer-list来初始化
class C1 |
C++11扩大了initializer-list的适用范围: 所有class都可以使用initializer-list初始化(包括standard container)
std::vector<std::string> v1{"123", "456"}; |
8. long long int
在C++03中最大的整型为long int, long int的长度范围为[32, 64], 所以每个编译器的实现都不同. C++11中引入了long long int, 其长度不会低于64位.
9. constexpr
C++一直就有常量的概念, 例如const. 但const的意义在于不可修改, 而没有强调常量的另一个特点: 可在编译期计算并获得结果. C++11引入了constexpr来描述一个可在编译期评估的variable, function和constructor
9.1 constexpr varible的条件
- 数据类型必须为LiteralType
- 必须被立即初始化
- 初始化表达式(initialization expression)必须为常量表达式(constant expression)
constexpr int v1 = 1; |
9.2 constexpr function的条件
- return type和parameter type必须是LiteralType
- 只包含一个return语句(非error, warning)
- function body可以包含其他的语句,但是这些语句不能在运行期起作用
- function body不能包含一些语句, 例如: goto, try-catch, asm之类的语句
- 可以是内部递归调用(recursive)
- 不能为virtual function
// constexpr function默认为inline function |
9.3 constexpr constructor
- constructor的参数类型为LiteralType
- class不能有虚基类(virtual base class)
- constructor不能含有try-block
- constructor可包含以下语句: 空语句, static_assert, typedef, using
- 对于constructor所在的class, 其对象必须初始化
10. nullptr
10.1 诞生原因
首先我们必须知道, C和C++中的NULL并不相同:
C中将NULL定义为((void*)0), 而C++将NULL定义为0. C++之所以将NULL修改为0, 是因为void*的隐式转换属性: 既可以指向任意类型指针, 又可转换为int类型
|
C++支持函数重载, 而void*会导致歧义, 所以就改用了0来表示NULL. 但由于NULL仍然没有类型, 所以还是会导致很多问题:
void f(char *); |
10.2 nullptr的属性
C++11正式引入nullptr, 并为nullptr赋予了一个独有的类型: nullptr_t. 其可以转换为其他指针类型和bool类型(为了兼容指针作为if判断的条件)
int *p1 = nullptr; |
正因为有了类型, 空指针也可以用来捕获异常
|
11. Delegating constructors
11.1 诞生原因
C++一直没有提供构造函数的委托机制(delegating constructor), 这导致不提供使用缺省参数, 也使得程序员必须维护多个构造函数, 且构造函数之间的代码大量冗余.
class X |
11.2 delegating constructors
C++11引入委托构造函数, 这样类内的其中一个构造函数(称为委托构造函数, delegating constructor)能在初始化列表(initializer list)中调用另一个构造函数(称为目标构造函数, target constructor). 当然, 委托构造函数也可被其他构造函数调用.
class X |
11.3 需要注意的问题
- delegating constructor最多只能调用一个target constructor, 且不能在调用target constructor时进行成员初始化
class X
{
private:
int i_;
public:
X(int i) : X(), i_(i) {} // error, an initializer for a delegating constructor must appear alone
X() {}
}; - delegating constructor的递归调用是undefined behavior
class X
{
public:
X(int i): X() {} // 可能报错, 也可能不报错
X(): X(42) {}
}; - target constructor中的语句执行完才会执行delegating constructor中的语句; target constructor中的临时变量不会作用到delegating constructor中
- C++03中一个object声明周期开始的标志为constructor执行完毕, C++11修改了这一规定: object生命周期开始的标志为其中一个constructor执行完毕. 因为C++03中并没有委托机制, 所以整个初始化只有一个constructor在执行; 而C++11中引入了委托机制, 所以初始化过程中不止一个constructor执行. 这样保证一旦初始化抛出异常, 会自动调用析构函数.
- template delegating constructor的类型推导不变
class X
{
private:
template <typename T>
X(T begin, T end) : l_(begin, end) {}
std::list<int> l_;
public:
X(std::vector<char> &);
X(std::deque<int> &);
};
X::X(std::vector<char> &v): X(v.begin(), v.end()) {}
X::X(std::deque<int> &d): X(d.begin(), d.end()) {}
int main()
{
std::vector<char> v{'a', 'b', 'c'};
std::deque<int> d{1, 2, 3};
X x1{v};
X x2{d};
}
12. Explicit virtual overrides
12.1 诞生原因
C++中的重写存在两个问题:
- C++中重写(override, 子类重写父类相同函数名和参数的virtual function)和重定义(redefine, 子类重定义父类相同函数名的non-virtual function)比较相似, 且编译器不会提醒你是否在重写/重定义.
class Base
{
virtual void func(int)
{
}
};
class Derived: public Base
{
void func() // 本想重写, 结果变成了重定义
{
}
}; - 当修改base class的virtual function的函数名或参数时, 程序员可能忘记修改derived class中的override function
class Base
{
public:
virtual void func(double) // func(int) -> func(double)
{
}
};
class Derived: public Base
{
public:
void func(int) // 忘记修改子类中func的参数类型
{
}
};
12.2 override关键字
C++11引入override关键字保证derived class中函数能够成功地重写base class中拥有相同签名的虚函数(相同的函数名和参数列表), 否则编译错误.
class Base |
13. Final specifier
13.1 诞生原因
override关键字解决了明确表示函数重写的问题, 但这也诞生了一个新的问题: 如何避免子类重写父类的函数
class AbstractLibrary |
13.2 final关键字
C++11引入final关键字来让防止父类的函数被子类重写
class AbstractLibrary |
14. extern template
14.1 分离式编译
C++支持分离式编译(分别编译项目中的各个源文件, 生成目标文件, 最后链接成可执行文件, 这种编译方式更适合模块化开发, 且编译速度更快). 但分离式编译并不适用于template, 原因如下:
- 分离式编译之所以能实现, 是因为每个编译单元(translation unit)中的非模板类和函数在编译阶段都会生成二进制码
/********** test.cpp **********/
int f()
{
return 1;
}
/********** main.cpp **********/
extern int f();
int main()
{
int i = f();
} - 一个template要想具现成二进制码, 不仅需要template的声明, 更需要其中函数定义的实现. 所以C++不支持通过template声明来隐式实例化一个template有两种解决方法: 将template的声明和实现放在一起, 或显示实例化(explicit instantation)一个类型为int的template, 这样编译器才会具现化其二进制码.
/********** test.cpp **********/
template <typename T>
class C
{
public:
T f(T);
};
/********** test.cpp **********/
template <typename T>
T C<T>::f(T x)
{
return x;
};
/********** main.cpp **********/
int main()
{
C<int> *pc = new C<int>(); // 编译器无法具现一个类型为int的template, 链接时报错
int i = pc->f(1);
}/********** test.cpp **********/
template <typename T>
T C<T>::f(T x)
{
return x;
};
template class C<int>; // explicit instantation
/********** main.cpp **********/
int main()
{
C<int> *pc = new C<int>();
int i = pc->f(1);
}
14.2 模板分离式编译导致的问题
当多个源文件使用同一个类型的同一template时, 编译时会隐式实例化每个源文件中的template, 并生成该类型template的二进制码, 而在链接时又会移除多余的实例化二进制码. 因此C++11引进了extern template来防止编译器生成冗余的二进制码, 从而加快编译和链接速度
/********** test.cpp **********/ |
15. Range-based for loop
C++11对for语句进行了扩展, 使得程序员能更简单的遍历list和container
int arr[5] = { 1, 2, 3, 4, 5 }; |
C风格的array, initializer list和STL中的container都可以使用range-based for来遍历元素. 其他还有begin()和end()的类型也可以使用range-based for.
16. Lambda functions and expressions
C++11提供了匿名函数的创建方法, 也称为lambda funtion(或lambda expression)
[Capture Clause](Parameter List) mutable throw() -> return_type { Function Body } |
lambda expression有以下几个部分组成:
- Capture Clause
Lambda支持使用cpture clause从当前范围内导入变量, 引入的方式可使用值传递和引用传递 - []: Capture nothing
- [&]: Capture any referenced variable by reference
- [=]: Capture any referenced variable by value
- [=, &foo]: Capture any referenced variable by value except foo
- [bar]: Capture bar by value and don't caputure anyone
- [this]: Capture the pointer of the enclosing class
- Parameter List
除了从外部引入变量, 还可以接受输入的参数. 参数也可使用值传递和引用传递int total = 0;
std::vector<int> vec = {1, 2, 3, 4, 5};
std::for_each(std::begin(vec), std::end(vec), [&total](int& x) {
total += x;
}); - mutable关键字
当lambda使用值传递时, 默认会有const限制. 可用mutable取消限制int i = 0;
auto f1 = [i]() { return ++i; }; // error, **total** is read-only variable
auto f2 = [i]() mutable { return ++i; }; // it works - Throw
Lambda支持异常处理[]()throw () { /* code that you don't expect to throw an exception*/ }
- Return Value
- 若无返回值, 则默认返回类型为void
[]{ std::cout << "Hello, World"; }; // 返回类型为void
- 若有返回值, 编译器会通过return statement的类型推到返回类型; 但如果编译器无法推导出返回类型, 则会默认返回类型为void
auto f1 = [](int x, int y) { return x + y; }; // 返回类型为int
// auto f2 = []() { return { 1, 2 }; }; // error, deduce return type from braced-list is not valid
auto f3 = []() -> std::initializer_list<int> { return { 1, 2 }; };
17. Strongly typed enumerations
17.1 诞生原因
C语言已经存在枚举类型(enum), C++为了兼容C自然也引入了枚举. 枚举提供了一种生成多个#define常量的功能, 并且相对于#define来说作用域更小.
|
但枚举存在以下问题:
- enum中枚举量(enumerator)暴露在外层作用域中, 因此一个作用域下的不同枚举不能有相同的枚举量
enum E1
{
Right = -1
};
enum E2
{
Right = 1 // error, redefinition of enumerator 'Right'
}; - 无法确定enum的大小, 虽然C++允许编译器使用任意整型类型来表示枚举量, 且没有规定是否有符号(signedness)
enum Selection
{
Multiple = 0xFFFF0000U // 根据编译器不同得出不同结果
}; - enum中的枚举量为整型常量, 所以可以进行各种隐式转换(implicit conversion):
enum E1
{
Right = 1,
Left
};
enum E2
{
Right_ = 1,
Left_
};
if (Right); // 隐式转换为boolean
double i = Right; // 隐式转换为double
if (E1::Right == E2::Right_); // 与其他enum的枚举量对比
17.2 强类型枚举
Strongly-typed enumeration含有以下属性:
- 若不声明类型, 则默认为int类型; 也可声明类型, 但必须为整形类型(bool, char, short, int, long), 且不限有无符号(signed, unsigned)
enum class E1
{
Right = -1
};
size_t size1 = sizeof(E1); // E1的大小为4
enum class E2 : long long int
{
Right = -1
};
size_t size2 = sizeof(E1); // E2的大小为8 - enum中的枚举量必须在其命名空间(namespace)内使用, 不可直接作用于外部作用域
enum class E1
{
Right
};
E1 r1 = Right; // error, undeclared identifier
E1 r2 = E1::Right; - enum中的枚举量不能隐式转换为POD(Plain Old Data), 但可以使用static_cast或强制转换为POD. 也不能与其他enum中的枚举量对比
enum class E1
{
Right
};
enum class E2
{
Right
};
int r1 = E1::Right; // error, E1 cannot be converted to int
int r2 = static_cast<int>(E1::Right);
if (E1::Right == E2::Right); // error
if ((int)E1::Right == (int)E2::Right);
18. Unrestricted unions
C++03允许union中包含non-static类对象, 但其class中不能有自定义的non-trivial special emmeber functions(包括constructor, destructor, copy constructor, copy-assignment operator)
class C1 |
C++11中移除了这个限制, 但依然不允许union中包含类对象的引用
class C1 |
19. Template aliases
C++可以用typedef关键字来为类型和函数定义别名(alias), 但并不支持为template起别名. C++11中支持了template alias(模板别名)
|
20. New string literals
C++03中提供了两种string literal(字符串字面量)来表示宽字符: wchar_t和L
const wchar_t c = L'a'; // wchar_t为2字节长度 |
C++11中引入了两种字符类型来表示16位和32位长度的字符: char16_t和char32_t, 还引入了三种字符串前缀标志来表示UTF-8, UTF-16和UTF-32: u8, u, U
const char *s1 = u8"abc"; |