译者:[email protected] 新浪微博@孙雨润 新浪博客 CSDN博客日期:2012年11月12日
原作:Scott Meyers
这些是Scott Meyers培训教程《新C++标准:C++0x教程》的官方笔记,培训课程的描述请见 http://www.aristeia.com/C++0x.html,版权信息请见 http://aristeia.com/Licensing/licensing.html.
漏洞和建议请发送邮件到 [email protected]. 翻译错误请发送邮件给[email protected] (译者注).
当可能时,">>"作为嵌套模板的结尾:
std::vector<std::list<int>> vi1; // fine in C++0x, error in C++98
在C++98中需要以空格分隔:
std::vector<std::list<int> > vi2; // fine in C++0x and C++98
衍生的语义推导变化:
const int n = … ; // n, m 是编译时常量
cosnt int m = … ;
std::array<int, n>m?n:m > a1; // error (as in C++98)
std::array<int, (n>m?n:m) > a2; // fine (as in C++98)
std::list<std::array<int, n>>2 >> L1; // error in ’98: 2 shifts; error in ’0x: 1st “>>” closes both templates
std::list<std::array<int, (n>>2) >> L2; // fine in C++0x, error in ’98 (2 shifts)
auto
修饰的变量具有它们初始化表达式的类型auto x1 = 10; // x1: int
std::map<int, std::string> m;
auto i1 = m.begin(); // i1: std::map<int, std::string>::iterator
const/volatile和引用/指针修饰符都可以添加在auto
上
const auto *x2 = &x1; // x2: const int*
const auto& i2 = m; // i2: const std::map<int, std::string>&
对于没有显示声明为引用的变量:初始化类型中的顶级const/volatile
会被忽略;初始化类型中的数组和函数名会退化成指针
const std::list<int> li;
auto v1 = li; // v1: std::list<int>
auto& v2 = li; // v2: const std::list<int>&
float data[BufSize];
auto v3 = data; // v3: float*
auto& v4 = data; // v4: float (&)[BufSize]
先前一段例子:
auto x1 = 10; // x1: int
std::map<int, std::string> m;
auto i1 = m.begin(); // i1: std::map<int, std::string>::iterator
const auto *x2 = &x1; // x2: const int* (const isn’t top-level)
const auto& i2 = m; // i2: const std::map<int, std::string>&
auto ci = m.cbegin(); // ci: std::map<int, std::string>::const_iterator
新的容器函数cbegin/cend/crbegin/crend
表示const_iterator
auto ci = m.cbegin(); // ci: std::map<int, std::string>::const_iterator
auto
具有和模板相似的推导能力template<typename T> void f(T t);
…
f(expr); // 从expr推导T的类型
auto v = expr; // 实质上做了类似的事情
除了对于新特性“花括号初始列表”,模板无法推导而auto
能推导。“与模板具有相似的推导能力”意味着右值引用会退化成左值引用
int x;
auto&& a1 = x; // x 是左值, 所以a1的类型是int&
auto&& a2 = std::move(x); // std::move(x) 是右值, 所以a2的类型是int&&
这在后续将详细讨论。
auto
可以同时声明多个相同类型变量void f(std::string& s)
{
auto temp = s, *pOrig = &s; // temp: std::string, pOrig: std::string*
}
再强调一遍,必须推导出相同类型的变量:
auto i = 10, d = 5.0; // error!
auto v1(expr); // 直接初始化
auto v2 = expr; // 赋值拷贝
构造函数是否声明为explicit
并不影响直接初始化,因为推导类型时不涉及类型转换;但如果拷贝构造函数声明为explicit
,会影响对auto
变量的赋值拷贝:
struct Explicit {
Explicit(){}
explicit Explicit(const Explicit&){}
} ex;
auto ex2 = ex; // Error
auto ex3(ex); // OK
for
循环std::vector<int> v;
…
for (int i : v) std::cout << i; // 将v中每一个元素赋值给i
对比C++0x
for ( iterVarDeclaration : expression ) statementToExecute
相当于
{
auto&& range = expression;
for (auto b = begin(range), e = end(range); b != e; ++b ) {
iterVarDeclaration = *b;
statementToExecute
}
}
reference/auto/const/volatile
for (int& i : v) std::cout << ++i; // 将v中每一个元素递增
for (auto i : v) std::cout << i; // 同上
for (auto& i : v) std::cout << ++i; // 同上
for (volatile int i : v) someOtherFunc(i); // 甚至 "volatile auto i"
如果对于T obj
,begin(obj)
和end(obj)
合法,那么基于范围的for
循环也适用。包括所有的C++0x容器、数组和valarray
、初始化lists、正则表达式、任何能提供合适的迭代器的用户定义类型。
std::unordered_multiset<std::shared_ptr<Widget>> msspw;
for (const auto& p : msspw) {
std::cout << p << '\n'; // print pointer value
}
short vals[ArraySize];
for (auto& v : vals) { v = -v; }
注意变量auto& p
使用了引用的方式,避免对shared_ptr
造成不必要的引用计数的操作。
【Note】新的基于范围的for
循环,不适用于while
和do...while
循环,也就是后两者没有这种新语义
nullptr
nullptr
是没有二义性的指针新的关键词,专指空指针。nullptr
的类型是std::nullptr
,其他指针类型可以使用static_cast
或者C风格类型转换转成nullptr
,结果永远是空指针。
void f(int *ptr); // 重载 ptr and int
void f(int val);
f(nullptr); // calls f(int*)
f(0); // calls f(int)
f(NULL); // 有可能 calls f(int),有可能报二义性错误
bool
const char *p = nullptr; // p is null
if (p) … // 编译通过,被转成false值
int i = nullptr; // 编译报错
以前NULL
和0
的使用方法保持兼容
int *p1 = nullptr; // p1 is null
int *p2 = 0; // p2 is null
int *p3 = NULL; // p3 is null
if (p1 == p2 && p1 == p3) … // 编译通过,expression值为true
nullptr
可以用于forwarding模板这一点与0, NULL
不同:
template<typename F, typename P> // 调用func,传入param参数
void Call(F func, P param) {
func(param);
}
void f(int* p); // some function to call
f(0); // fine
f(nullptr); // also fine
logAndCall(f, 0); // error! P 被推导成int, f(int) 非法
logAndCall(f, NULL); // error!
logAndCall(f, nullptr); // fine, P 被推导成 std::nullptr_t, f(std::nullptr_t) is okay
char16_t // 16-bit character (if available) 和 uint_least16_t 类似
char32_t // 32-bit character (if available) 和 uint_least32_t 类似
在字符前使用前缀,标识字符类型
u'x' // 'x' as a char16_t using UCS-2
U'x' // 'x' as a char32_t using UCS-4/UTF-32
C++98的语法仍然可用
'x' // 'x' as a char
L'x' // 'x' as a wchar_t
u"UCS-2 string literal" // ⇒ char16_ts in UTF-16
U"UCS-4 string literal" // ⇒ char32_ts in UCS-4/UTF-32
"Ordinary/narrow string literal" // "ordinary/narrow" ⇒ chars
L"Wide string literal" // "wide" ⇒ wchar_ts
u8"UTF-8 string literal" // ⇒ chars in UTF-8
stl
中相应的string
类
std::string s1; // std::basic_string<char>
std::wstring s2; // std::basic_string<wchar_t>
std::u16string s3; // std::basic_string<char16_t>
std::u32string s4; // std::basic_string<char32_t>
std::codecvt
在C++98中能够使wchar_t
与char
互转:
std::codecvt<wchar_t, char, std::mbstate_t>
在C++0x中新增了如下功能:
UTF-16 ⇄ UTF-8 (std::codecvt<char16_t, char, std::mbstate_t>)
UTF-32 ⇄ UTF-8 (std::codecvt<char32_t, char, std::mbstate_t>)
UTF-8 ⇄ UCS-2, UTF-8 ⇄ UCS-4 (std::codecvt_utf8)
UTF-16 ⇄ UCS-2, UTF-16 ⇄ UCS-4 (std::codecvt_utf16)
// Behaves like std::codecvt<char16_t, char, std::mbstate_t>.
UTF-8 ⇄ UTF-16 (std::codecvt_utf8_utf16)
对特殊字符\"/等不需要再手动escape:
std::string noNewlines(R"(\n\n)");
std::string cmd(R"(ls /home/docs | grep ".pdf")");
std::string withNewlines(R"(Line 1 of the string...
Line 2...
Line 3)");
R可以与任意字符编码:
LR"(Raw Wide string literal \t (without a tab))"
u8R"(Raw UTF-8 string literal \n (without a newline))"
uR"(Raw UTF-16 string literal \\ (with two backslashes))"
UR"(Raw UTF-32 string literal \u2620 (without a code point))"
需要注意的是R
必须放在表示字符编码的字母后边。
// "operator()"|"operator->"
std::regex re1(R"!("operator\(\)"|"operator->")!");
// "(identifier)"
std::regex re2(R"xyzzy("\([A-Za-z_]\w*\)")xyzzy");
默认的字符串R"(XXXX)";
里的左右括号之间的部分;如果在"(
之间插入一个不超过16个字符不含空格的字符串,则会以这段字符串为界,如re2的xyzzy
注意初始化不等于赋值,例如const对象不能被赋值但是可以被初始化
const int y(5); // “direct initialization” syntax
const int x = 5; // “copy initialization” syntax
int arr[] = { 5, 10, 15 }; // brace initialization
struct Point1 { int x, y; };
const Point1 p1 = { 10, 20 }; // brace initializtion
class Point2 {
public:
Point2(int x, int y);
};
const Point2 p2(10, 20); // function call syntax
容器的初始化需要另一个容器:
int vals[] = { 10, 20, 30 };
const std::vector<int> cv(vals, vals+3); // init from another container
成员变量和堆上数组无法初始化:
class Widget {
public:
Widget(): data(???) {}
private:
const int data[5]; // not initializable
};
const float * pData = new const float[4]; // not initializable
{}
作为统一初始化方式{}
初始化可以用在所有地方:
const int val1 {5};
const int val2 {5};
int a[] { 1, 2, val1, val1+val2 };
struct Point1 { … }; // as before
const Point1 p1 {10, 20};
class Point2 { … }; // as before
const Point2 p2 {10, 20}; // calls Point2 ctor
const std::vector<int> cv { a[0], 20, val2 };
class Widget {
public:
Widget(): data {1, 2, a[3], 4, 5} {}
private:
const int data[5];
};
const float * pData = new const float[4] { 1.5, val1-val2, 3.5, 4.5 };
当通过花括号{}
初始化成员变量时,花括号可以包含在小括号()
中:
Widget(): data({1, 2, a[3], 4, 5}) {}
一些以前不敢想象的方式:
Point2 makePoint() { return { 0, 0 }; } // return expression; calls Point2 ctor
void f(const std::vector<int>& v); // func. declaration
f({ val1, val2, 10, 20, 30 }); // function argument
严格说来聚合类型的定义比上边描述的稍微复杂一些,标准的定义是:“聚合是一个数组或者这样的类:没有用户提供的构造函数,没有非静态成员的默认初始化函数,没有private或者protected的数据成员,没有基类,没有虚函数。”
union
统一初始化语法可以用在union
上,但是只有union
的第一个成员会被初始化
union u { int a; char* b; };
u a = { 1 }; // okay
u d = { 0, "asdf" }; // error
u e = { "asdf" }; // error (can’t initialize an int with a char array)
如果初始化元素数量超过容器大小,则编译报错,如果小于容器大小,剩下的对象进行"值初始化":内置类型初始为0、自定义类型调用构造函数、没有构造函数则对自定义类型的成员进行递归的值初始化
struct Point1 { int x, y; }; // as before
const Point1 p1 = { 10 }; // same as { 10, 0 }
const Point1 p2 = { 1, 2, 3 }; // error! too many initializers
long f();
std::array<long, 3> arr = { 1, 2, f(), 4, 5 }; // error! too many initializers
.x
的初始化方式被取消struct Point {
int x, y, z;
};
Point p { .x = 5, .z = 8 }; // error!
class Point2 { // as before
public:
Point2(int x, int y);
};
short a, b;
const Point2 p1 {a, b}; // same as p1(a, b)
const Point2 p2 {10}; // error! too few ctor args
const Point2 p3 {5, 10, 20}; // error! too many ctor args
对容器也一样有效,注意和聚合类型初始化的区别
std::vector<int> v { 1, a, 2, b, 3 }; // calls a vector ctor
std::unordered_set<float> s { 0, 1.5, 3 }; // calls an unordered_set ctor
= {}
进行赋值大部分是OK的:
const int val1 = {5};
const int val2 = {5};
int a[] = { 1, 2, val1, val1+val2 };
struct Point1 { … };
const Point1 p1 = {10, 20};
class Point2 { … };
const Point2 p2 = {10, 20};
const std::vector<int> cv = { a[0], 20, val2 };
下面是非法的情况:
class Widget {
public:
Widget(): data = {1, 2, a[3], 4, 5} {} // error!
private:
const int data[5];
};
const float * pData = new const float[4] = { 1.5, val1-val2, 3.5, 4.5 }; // error!
Point2 makePoint() { return = { 0, 0 }; } // error!
void f(const std::vector<int>& v); // as before
f( = { val1, val2, 10, 20, 30 }); // error!
注意这种语法无法调用以explicit
声明的构造函数:
class Widget {
public:
explicit Widget(int);
};
Widget w1(10); // okay, direct init: explicit ctor callable
Widget w2{10}; // 同上
Widget w3 = 10; // error! copy init: explicit ctor not callable
Widget w4 = {10}; // 同上
因此推荐养成不适用= {}
而只是用 {}
的习惯
{}
初始化方式禁止隐式有损转换所谓有损转换是指:目标类型无法表示源类型的所有值,或者编译器不能保证源值会在目标类型能表达的范围之内。C++98允许赋值时隐式有损转换,在C++0x总会编译报错:
struct Point { int x, y; };
Point p1 { 1, 2.5 }; // fine in C++98: implicit double ⇒ int conversion; error in C++0x
Point p2 { 1, static_cast<int>(2.5) }; // fine in both C++98 and C++0x
这会导致:直接使用构造函数,与使用{}
初始化、由编译器间接调用构造函数,两种方法有些细微差别:
class Widget {
public:
Widget(unsigned u);
};
int i;
Widget w1(i); // okay, implicit int ⇒ unsigned
Widget w2 {i}; // error! int ⇒ unsigned narrows
unsigned u;
Widget w3(u); // fine
Widget w4 {u}; // also fine, same as w3’s init.
初始化list与聚合类型初始化相似
int x, y;
int a[] { x, y, 7, 22, -13, 44 }; // 数组
std::vector<int> v { 99, -8, x-y, x*x }; // std. library type
Widget w { a[0]+a[1], x, 25, 16 }; // 自定义类型
但不仅有初始化的功能:
std::vector<int> v {}; // initialization
v.insert(v.end(), { 99, 88, -1, 15 }); // 一次插入多个元素
v = { 0, 1, x, y }; // repace的动作
任何函数都可以使用初始化list作为参数。
{}
转换成std::initializer_list
对象std::initializer_list
作为参数std::initializer_list
存储初始元素的值,并提供几个函数:size()/begin()/end()
注意initializer_list
与其他容器不同,没有rbegin/rend/cbegin/cend/crbegin/crend
几个迭代游标。
标准库中initializer_list
对象永远按值传递。
Case1:
#include <initializer_list> // necessary header
std::u16string getName(int ID); // lookup name with given ID
class Widget {
public:
Widget(std::initializer_list<int> nameIDs){
names.reserve(nameIDs.size());
for (auto id: nameIDs) names.push_back(getName(id));
}
private:
std::vector<std::u16string> names;
};
...
// copies values into an array wrapped by an initializer_list passed to the Widget ctor.
Widget w { a[0]+a[1], x, 25, 16 };
这个例子表达的意思是:Widget
对象在构造过程中,将初始化列表中的ID
转成UTF-16
的字符串存储。注意在getName
函数中可以使用move语义提高效率。
Case2:std::initializer_list
可以与其他参数一起使用:
class Widget {
public:
Widget(const std::string& name, double epsilon, std::initializer_list<int> il);
…
};
std::string name("Buffy");
// same as Widget w(name, 0.5, std::initializer_list({5,10,15}));
Widget w { name, 0.5, {5, 10, 15} };
Case3:可以用作模板
class Widget {
public:
template<typename T> Widget(std::initializer_list<T> il);
...
};
...
Widget w1 { -55, 25, 16 }; // fine, T = int
注意推导过程中出现元素类型不一致会报错:
Widget w2 { -55, 2.5, 16 }; // error, T can’t be deduced
initializer_list
与统一初始化方式{}
的冲突如果一个类实现了initializer_list
为参数的构造函数,编译器为{}
优先匹配这个构造函数。例如
class Widget {
public:
Widget(double value, double uncertainty); // #1
Widget(std::initializer_list<double> values); // #2
…
};
double d1, d2;
…
Widget w1 { d1, d2 }; // calls #2
Widget w2(d1, d2); // calls #1
进一步理解:
class Widget {
public:
Widget(double value, double uncertainty); // #1
Widget(std::initializer_list<std::string> values); // #2
…
};
double d1, d2;
…
Widget w1 { d1, d2 }; // 调用失败,编译报错。因为没有double ⇒ string的转换
Widget w2(d1, d2); // still calls #1
也就是说如果实现了initializer_list
参数的版本,那么{}
永远考虑这个版本,即便出错。
initializer_list
之间的冲突如果一个对象实现了多个版本的以initializer_list
为参数的构造函数,编译器会转换成本最小的版本。
class Widget {
public:
Widget(std::initializer_list<int>); // #1
Widget(std::initializer_list<double>); // #2
Widget(std::initializer_list<std::string>); // #3
Widget(int, int, int); // due to above ctors, this ctor not
}; // considered for “{... }” args
// int ⇒ double same rank as double ⇒ int, so ambiguous
Widget w1 { 1, 2.0, 3 };
// float ⇒ double better than float ⇒ int, so calls #2
Widget w2 { 1.0f, 2.0, 3.0 };
std::string s;
Widget w3 { s, "Init", "Lists" }; // calls #3
如果即使最优的选择也包含有损转换,则编译报错:
class Widget {
public:
Widget(std::initializer_list<int>);
Widget(int, int, int); // due to above ctor, not
}; // considered for “{... }” args
Widget w { 1, 2.0, 3 }; // error! double ⇒ int narrows
{}
初始化可以用在任何地方,语义上区分聚合与非聚合initializer_list
对象允许初始化list传给函数,并不限制构造函数,例如std::vector::insert