核心C++11已经有了很大的改变。现在它支持lambda表达式,自动类型推断,统一的初始化语法,委托构造函数,已删除和默认函数声明,nullptr,以及最重要的右值引用——一种预言将会改变创造和处理对象方式的技术。
C++11标准库同样增加了新的算法,新的容器类,原子运算,类型特性,正则表达式,新的智能指针,async()功能以及多线程库。
Lambda 表达式
[capture](parameters)->return-type {body}
有多少大写字母。使用for_each()遍历字符数组,下面的 lambda 表达式判断每一个字母是不是大写的。
-
int main()
-
{
-
char s[] = "Hello World!";
-
int Uppercase = 0; // lambda对其进行修改
-
for_each(s, s+sizeof(s), [&Uppercase] (char c) {
-
if (isupper(c))
-
Uppercase++;
-
});
-
cout << Uppercase << " uppercase letters in: " << s << endl;
-
}
自动类型推断auto和decltype
C++11利用这一地点,允许你以不指定类型的方式声明对象:
-
auto x = 0; // 因为 0 是 int,所以 x 类型是 int
-
auto c = 'a'; // char
-
auto d = 0.5; // double
-
auto national_debt = 14400000000000LL; //long long
当对象类型太冗长或者是在模板中自动生成时,自动类型推断尤其有用。考虑:
-
vector<int>::const_iterator ci = vi.begin();
auto ci = vi.begin();
C++11 为捕获对象或表达式的类型提供了类似的机制。新的运算符decltype接收一个表达式并“返回”其类型:
-
const vector<int> vi;
-
typedef decltype (vi.begin()) CIT;
-
CIT another_const_iterator;
统一初始化语法
C++至少有四种不同的初始化方法,有些是重叠的。
带有括号的初始化类似:
-
std::string s("hello");
-
int m = int(); // 默认初始化
在某些特殊情况,你也可以使用“=”达到相同目的:
-
std::string s = "hello";
-
int x = 5;
对于 POD(Plain Old Data,具有 C 兼容特点)聚合,可以使用大括号:
-
int arr[4] = {0,1,2,3};
-
struct tm today = {0};
最后,在构造函数中使用成员初始化器:
-
struct S
-
{
-
int x;
-
S(): x(0) {}
-
};
初始化操作的多种变体是令人感觉困扰的重要原因之一,不仅仅对于新手而言。更糟糕的是,在C++03你不能对使用new[]分配空间的POD数组进行数组成员和POD本身的初始化。C++11使用统一的大括号标记清楚了这种混乱:
-
class C
-
{
-
int a;
-
int b;
-
public:
-
C(int i, int j);
-
};
-
C c {0,0}; // C++11可用,等价于C c(0,0);
-
int* a = new int[3] { 1, 2, 0 }; // C++11可用
-
class X
-
{
-
int a[4];
-
public:
-
X() : a{1,2,3,4} {} // C++11 可用,成员数组初始化器
-
};
对于容器,现在可以跟一长串push_back()调用说再见了。在C++11你可以直观地初始化容器:vector<string> vs = { "first", "second", "third"};
-
map singers = { {"Lady Gaga", "+1 (212) 555-7890"},
-
{"Beyonce Knowles", "+1 (212) 555-0987"}};
类似的,C++11支持数据成员的类内初始化:
-
class C
-
{
-
int a = 7; // C++11可用
-
public:
-
C();
-
};
删除和默认函数
具备如下形式的函数成为默认函数(defaulted function):
-
struct A
-
{
-
A() = default; // C++11
-
virtual ~A() = default; // C++11
-
};
=default;部分说明,编译器生成该函数的一个默认实现。默认函数有两个优势:比手工实现更有效;把程序员从手动定义这些函数的繁琐中解放出来。
与默认函数相反的是删除函数:
int func() = delete;
删除函数对防止对象复制很有用。回忆一下,C++为每个类自动声明一个拷贝构造函数和赋值运算符。为禁止复制,需要将这两个特殊的成员函数声明为=delete:
-
struct NoCopy
-
{
-
NoCopy & operator =( const NoCopy & ) = delete;
-
NoCopy ( const NoCopy & ) = delete;
-
};
-
NoCopy a;
-
NoCopy b(a); // 编译错误,拷贝构造函数已经被删除
nullptr
终于,C++有了一个代表空指针常量的关键字。nullptr替换了充满bug的NULL宏,以及被用于空指针好多年的字面常量0。nullptr是强类型的:
-
void f(int); // #1
-
void f(char *);// #2
-
// C++03
-
f(0); // 调用哪一个 f?
-
// C++11
-
f(nullptr) // 无歧义,调用 #2
nullptr适用于所有指针类型,包括函数指针和成员指针:
-
const char *pc = str.c_str(); // 数据指针
-
if (pc != nullptr)
-
cout << pc << endl;
-
int (A::*pmf)() = nullptr; // 成员函数指针
-
void (*pmf)() = nullptr; // 函数指针
委托构造函数
在 C++11中,构造函数可以调用同一个类中另外的构造函数:
-
class M // C++11委托构造函数
-
{
-
int x, y;
-
char *p;
-
public:
-
M(int v) : x(v), y(0), p(new char [MAX]) {} // #1 目标
-
M(): M(0) {cout << "delegating ctor" << endl;} // #2 委托
-
};
构造函数#2是委托构造函数,调用了目标构造函数#1。
右值引用
C++03的引用类型只能绑定左值。C++11引入了新的引用类型——右值引用。右值引用可以绑定右值,例如临时变量和字面常量。
增加右值引用的主要原因是移动语义。不同于传统的拷贝,移动的含义是目标对象占有源对象的资源,将源对象设置为“空”状态。在这种情景下,拷贝一个对象既昂贵又不必要,应该使用移动操作符。为感受移动语义在性能上的优势,考虑交换字符串。一个原始的实现类似于:
-
void naiveswap(string &a, string & b)
-
{
-
string temp = a;
-
a = b;
-
b = temp;
-
}
这种实现很昂贵。拷贝字符串需要分配内存,将字符从源对象复制到目标对象。相比而言,移动字符串仅仅意味着交换两个数据成员,不需要分配内存、复制字符数组和释放内存:
-
void moveswapstr(string& empty, string & filled)
-
{
-
// 伪代码,体会思想
-
size_t sz = empty.size();
-
const char *p = empty.data();
-
// 移动filled的资源到empty
-
empty.setsize(filled.size());
-
empty.setdata(filled.data());
-
// 移动empty的资源到filled
-
filled.setsize(sz);
-
filled.setdata(p);
-
}
如果你正在实现一个支持移动的类,需要声明一个移动构造函数和移动复制运算符:
-
class Movable
-
{
-
Movable (Movable&&); // 移动构造函数
-
Movable&& operator=(Movable&&); // 移动复制运算符
-
};
C++11 标准库大量使用了移动语义。许多算法和容器也为移动语义做了优化。
C++11 标准库
2003年,C++以库技术报告 1(TR1)的形式经历了一次大型重构。TR1包含了新的容器类(unordered_set,unordered_map,unordered_multiset和unordered_multimap)和许多新的库,例如正则表达式,元祖,函数对象包装器。下面是C++11标准库的特性:
线程库
毫无疑问,从程序员角度看,C++11最重要的改进就是并发。C++11有一个thread类,描述一个执行线程、promise和future(用于并发环境下同步的对象),用于发起并发任务的模板函数async()和用于声明线程独立的数据的存储类型thread_local。快速了解C++11线程库,请阅读Anthony Williams的文章Simpler Multithreading in C++0x。
新的智能指针类
C++98只定义了一个智能指针类,auto_ptr,而这个类现在已经被废弃了。C++11包含了新的智能指针类:shared_ptr和最近新加的unique_ptr。
新的算法
C++11标准库定义了模拟集合论操作的新的算法all_of()、any_of()和none_of()。下面几行将谓词ispositive()应用于范围[first, first+n),然后使用all_of()、any_of()和none_of()检测范围的属性:
-
#include <algorithm>
-
// C++11 代码
-
// 所有元素都是正数吗?
-
all_of(first, first+n, ispositive()); // false
-
// 至少有一个元素是正数吗?
-
any_of(first, first+n, ispositive()); // true
-
// 没有元素是正数?
-
none_of(first, first+n, ispositive()); // false
还有一个新的copy_n算法。使用copy_n()将一个有 5 个元素的数组复制到另外一个可说是小菜一碟:
-
#include <algorithm>
-
int source[5] = { 0, 12, 34, 50, 80 };
-
int target[5];
-
// 从源数组到目的数组拷贝 5 个元素
-
copy_n(source, 5, target);
iota()算法创建一个递增的数字范围,就像首先给*first赋初始值,然后使用++递增。在下面的代码中,iota()将连续数值{10,11,12,13,14}赋值给数组a,将{'a', 'b', 'c'}赋值给字符数组c。
-
include <numeric>
-
int a[5] = {0};
-
char c[3] = {0};
-
iota(a, a+5, 10); // 将 a 修改为 { 10, 11, 12, 13, 14 }
-
iota(c, c+3, 'a'); // {'a', 'b', 'c'}
C++11依然缺少一些有用的库,例如 XML API,socket,GUI,反射——当然,还有合理的自动垃圾回收器。