这些特色存在的主要目的是为了使C++(www.cppentry.com)能够更容易使用。 举凡可以增进类型安全,减少代码重复,不易误用代码之类的。
初始化列表
标准C++(www.cppentry.com)从C带来了初始化列表(initializer list)的概念。这个构想是结构或是数组能够依据成员在该结构内定义的顺序通过给予的一串引数来产生。这些初始化列表是递归的,所以结构的数组或是包含其他结构的结构可以使用它们。这对静态列表或是仅是把结构初始化为某值而言相当有用。C++(www.cppentry.com)有构造函数,能够重复对象的初始化。但单单只有那样并不足以取代这项特色的所有机能。除了这些对象必须遵守POD的定义的限制条件,标准C++(www.cppentry.com)允许在结构或类型上使用这项机能;非POD的类型不能使用,就连相当有用的C++(www.cppentry.com)-style容器像是std::vector也不行。
C++(www.cppentry.com)11将会把初始化列表的概念绑到类型上,称作std::initializer_list
。这允许构造函数或其他函数像参数般地使用初始化列表。举例来说:class SequenceClass
{
public:
SequenceClass(std::initializer_list<int> list);
};
这将允许SequenceClass
由一连串的整数构造,就像:SequenceClass someVar = {1, 4, 5, 6};
这个构造函数是种特殊的构造函数,称作初始化列表构造函数。有着这种构造函数的类型在统一初始化的时候会被特别对待。
类型std::initializer_list<>
是个第一级的C++(www.cppentry.com)11标准程序库类型。然而他们只能够经由C++(www.cppentry.com)11编译器通过{}语法的使用被静态地构造 。这个列表一经构造便可复制,虽然这只是copy-by-reference。初始化列表是常数;一旦被创建,其成员均不能被改变,成员中的数据也不能够被变动。因为初始化列表是真实类型,除了类型构造式之外还能够被用在其他地方。正规的函数能够使用初始化列表作为引数。例如:
void FunctionName(std::initializer_list<float> list);
FunctionName({1.0f, -3.45f, -0.4f});
标准容器也能够以这种方式初始化:
vector<string> v = { "xyzzy", "plugh", "abracadabra" };
统一的初始化
标准 C++(www.cppentry.com) 在初始化类型方面有着许多问题。初始化类型有数种方法,而且交换使用时不会都产生相同结果。传统的建构式语法,看起来像是函数声明,而且为了能使编译器不会弄错必须采取一些步骤。只有集合体和 POD 类型能够被集合式的初始化(使用 SomeType var = {/*stuff*/};
).C++(www.cppentry.com)11 将会提供一种统一的语法初始化任意的对象,它扩充了初始化串行语法:
struct BasicStruct
{
int x;
float y;
};
struct AltStruct
{
AltStruct(int _x, float _y) : x(_x), y(_y) {}
private:
int x;
float y;
};
BasicStruct var1{5, 3.2f};
AltStruct var2{2, 4.3f};
var1
的初始化的运作就如同 C-style 的初始化串行。每个公开的变量将被对应于初始化串行的值给初始化。隐式类型转换会在需要的时候被使用,这里的隐式类型转换不会产生范围缩限 (narrowing)。要是不能够转换,编译便会失败。(范围缩限 (narrowing):转换后的类型无法表示原类型。如将 32-bit 的整数转换为 16-bit 或 8-bit 整数,或是浮点数转换为整数。) var2 的初始化则是简单地调用建构式。统一的初始化建构能够免除具体指定特定类型的必要:
struct IdString
{
std::string name;
int identifier;
};
IdString var3{"SomeName", 4};
该语法将会使用 const char *
参数初始化 std::string 。你也可以做像下面的事:IdString GetString()
{
return {"SomeName", 4}; // 注意這裡不需要明確的型別
}
统一初始化不会取代建构式语法。仍然会有需要用到建构式语法的时候。如果一个类型拥有初始化串行建构式(TypeName(initializer_list<SomeType>);
),而初始化串行符合 sequence 建构式的类型,那么它比其他形式的建构式的优先权都来的高。C++(www.cppentry.com)11 版本的 std::vector 将会有初始化串行建构式。这表示:std::vector<int> theVec{4};
这将会调用初始化串行建构式,而不是调用std::vector
只接受一个尺寸参数产生相应尺寸 vector 的建构式。要使用这个建构式,用户必须直接使用标准的建构式语法。
类型推导
在标准 C++(www.cppentry.com)(和 C ),使用变量必须明确的指出其类型。然而,随着模版类型的出现以及模板元编程(www.cppentry.com)的技巧,某物的类型,特别是函数定义明确的回返类型,就不容易表示。在这样的情况下,将中间结果存储于变量是件困难的事,可能会需要知道特定的元编程(www.cppentry.com)程序库的内部情况。
C++(www.cppentry.com)11 提供两种方法缓解上述所遇到的困难。首先,有被明确初始化的变量可以使用 auto
关键字。这会依据该初始化子(initializer)的具体类型产生变量:auto someStrangeCallableType = boost::bind(&SomeFunction, _2, _1, someObject);
auto otherVariable = 5;
someStrangeCallableType
的类型就是模板函数 boost::bind 对特定引数所回返的类型。作为编译器语义分析责任的一部份,这个类型能够简单地被编译器决定,但用户要通过查看来判断类型就不是那么容易的一件事了。otherVariable
的类型同样也是定义明确的,但用户很容易就能判别。它是个 int(整数),就和整数字面值的类型一样。除此之外,decltype
能够被用来在编译期决定一个表示式的类型。举例:int someInt;
decltype(someInt) otherIntegerVariable = 5;
decltype
和 auto 一起使用会更为有用,因为 auto 变量的类型只有编译器知道。然而 decltype 对于那些大量运用操作符重载和特化的类型的代码的表示也非常有用。auto
对于减少冗赘的代码也很有用。举例而言,程序员不用写像下面这样:for (vector<int>::const_iterator itr = myvec.cbegin(); itr != myvec.cend(); ++itr)
而可以用更简短的
for (auto itr = myvec.cbegin(); itr != myvec.cend(); ++itr)
这项差异随着程序员开始嵌套容器而更为显著,虽然在这种情况下 typedef
是一个减少代码的好方法。decltype
所表示的类型可以和 auto 推导出来的不同。#include <vector>
int main()
{
const std::vector<int> v(1);
auto a = v[0];// a 為 int 型別
decltype(v[0])b; // b 為 const int& 型別,即
// std::vector<int>::operator[](size_type)const 的回返型別
auto c = 0; // c 為 int 型別
auto d = c; // d 為 int 型別
decltype(c) e; // e 為 int 型別,c 實體的型別
decltype((c)) f = e; // f 為 int& 型別,因為(c)是左值
decltype(0) g; // g為int型別,因為0是右值
}
学习
以范围为基础的 for 循环
Boost C++(www.cppentry.com) 定义了许多"范围 (range) "的概念。范围表现有如受控制的串行 (list),持有容器中的两点。有序容器是范围概念的超集 (superset),有序容器中的两个迭代器(iterator) 也能定义一个范围。这些概念以及操作的算法,将被并入 C++(www.cppentry.com)11 标准程序库。不过 C++(www.cppentry.com)11 将会以语言层次的支持来提供范围概念的效用。
for
述句将允许简单的范围迭代:int my_array[5] = {1, 2, 3, 4, 5};
for(int &x : my_array)
{
x *= 2;
}
上面 for
述句的第一部份定义被用来做范围迭代的变量,就像被声明在一般 for 循环的变量一样,其作用域仅只于循环的范围。而在":"之后的第二区块,代表将被迭代的范围。这样一来,就有了能够允许 C-style 数组被转换成范围概念的概念图。这可以是 std::vector,或是其他符合范围概念的对象。
Lambda函数与表示式
在标准 C++(www.cppentry.com),特别是当使用 C++(www.cppentry.com) 标准程序库算法函数诸如 sort
和 find,用户经常希望能够在算法函数调用的附近定义一个临时的述部函数(又称谓词函数,predicate function)。由于语言本身允许在函数内部定义类型,可以考虑使用函数对象,然而这通常既麻烦又冗赘,也阻碍了代码的流程。此外,标准 C++(www.cppentry.com) 不允许定义于函数内部的类型被用于模板,所以前述的作法是不可行的。C++(www.cppentry.com)11 对 lambda 的支持可以解决上述问题。
一个 lambda 函数可以用如下的方式定义:
[](int x, int y) { return x + y; }
这个不具名函数的回返类型是 decltype(x+y)
。只有在 lambda 函数符合"return expression"的形式下,它的回返类型才能被忽略。在前述的情况下,lambda 函数仅能为一个述句。在一个更为复杂的例子中,回返类型可以被明确的指定如下:
[](int x, int y) -> int { int z = x + y; return z + x; }
本例中,一个暂时的变量 z
被创建用来存储中间结果。如同一般的函数,z 的值不会保留到下一次该不具名函数再次被调用时。如果 lambda 函数没有传回值(例如 void
),其回返类型可被完全忽略。 定义在与 lambda 函数相同作用域的变量参考也可以被使用。这种的变量集合一般被称作 closure (闭包)。[] // 沒有定義任何變數。使用未定義變數會導致錯誤。
[x, &y] // x 以傳值方式傳入(預設),y 以傳參考方式傳入。
[&] // 任何被使用到的外部變數皆隱式地以參考方式加以引用。
[=] // 任何被使用到的外部變數皆隱式地以傳值方式加以引用。
[&, x] // x 顯示地以傳值方式加以引用。其餘變數以參考方式加以引用。
[=, &z] // z 顯示地以參考方式加以引用。其餘變數以傳值方式加以引用。
closure 被定义与使用如下:
std::vector<int> someList;
int total = 0;
std::for_each(someList.begin(), someList.end(), [&total](int x) {
total += x;
});
std::cout << total;
上例可计算 someList
元素的总和并将其印出。 变量 total 是 lambda 函数 closure 的一部分,同时它以引用方式被传递入谓词函数, 因此它的值可被 lambda 函数改变。若不使用引用的符号&
,则代表变量以传值的方式传入 lambda 函数。 让用户可以用这种表示法明确区分变量传递的方法:传值,或是传参考。 由于 lambda 函数可以不在被声明的地方就地使用(如置入 std::function 对象中); 这种情况下,若变量是以传参考的方式连结到 closure 中,是无意义甚至是危险的行为。若 lambda 函数只在定义的作用域使用, 则可以用 [&]
声明 lambda 函数, 代表所有引用到 stack 中的变量,都是以参考的方式传入, 不必一一显式指明:std::vector<int> someList;
int total = 0;
std::for_each(someList.begin(), someList.end(), [&](int x) {
total += x;
});
变量传入 lambda 函数的方式可能随实做有所变化,一般期望的方法是 lambda 函数能保留其作用域函数的 stack 指针,借此访问区域变量。
若使用 [=]
而非 [&],则代表所有的参考的变量都是传值使用。对于不同的变量,传值或传参考可以混和使用。 比方说,用户可以让所有的变量都以传参考的方式使用,但带有一个传值使用的变量:
int total = 0;
int value = 5;
[&, value](int x) { total += (x * value); };
total
是传参考的方式传入 lambda 函数,而 value 则是传值。若一个 lambda 函数被定义于某类型的成员函数中,会被当作该类型的 friend。像这样的 lambda 函数可以使用该类型对象的参考,并且能够访问其内部的成员。
[](SomeType *typePtr) { typePtr->SomePrivateMemberFunction(); };
这只有当该 lambda 函数创建的作用域是在 SomeType
的成员函数内部时才能运作。
在成员函数中指涉对象的 this 指针,必须要显式的传入 lambda 函数, 否则成员函数中的 lambda 函数无法使用任何该对象的变量或函数。
[this]() { this->SomePrivateMemberFunction(); };
若是 lambda 函数使用 [&] 或是 [=] 的形式,this
在 lambda 函数即为可见。lambda 函数是编译器从属类型的函数对象; 这种类型名称只有编译器自己能够使用。如果用户希望将 lambda 函数作为参数传入,该类型必须是模版类型,或是必须创建一个std::function
去获取 lambda 的值。使用 auto 关键字让我们能够存储 lambda 函数:auto myLambdaFunc = [this]() { this->SomePrivateMemberFunction(); };
auto myOnheapLambdaFunc = new auto([=] { /*...*/ });
但是,如果 lambda 函数是以参考的方式获取到它所有的 closure 变量,或者是没有 closure 变量,那么所产生的函数对象会被给予一个特殊的类型:std::reference_closure<R(P)>
,其中 R(P) 是包含回返类型的函数签名。比起由 std::function 获取而来,这会是lambda函数更有效率的代表:std::reference_closure<void()> myLambdaFunc = [this]() { this->SomePrivateMemberFunction(); };
myLambdaFunc();
另一种的函数语法
标准C 函数声明语法对于C语言已经足够。 演化自 C 的 C++(www.cppentry.com) 除了 C 的基础语法外,又扩充额外的语法。 然而,当 C++(www.cppentry.com) 变得更为复杂时,它暴露出许多语法上的限制, 特别是针对函数模板的声明。 下面的示例,不是合法的 C++(www.cppentry.com)03:
template< typename LHS, typename RHS>
Ret AddingFunc(const LHS &lhs, const RHS &rhs) {return lhs + rhs;} //Ret的型別必須是(lhs+rhs)的型別
Ret
的类型由 LHS与RHS相加之后的结果的类型来决定。 即使使用 C++(www.cppentry.com)11 新加入的 decltype 来声明 AddingFunc 的返回类型,依然不可行。template< typename LHS, typename RHS>
decltype(lhs+rhs) AddingFunc(const LHS &lhs, const RHS &rhs) {return lhs + rhs;} //不合法的 C++(www.cppentry.com)11
不合法的原因在于lhs
及 rhs 在定义前就出现了。 直到剖析器解析到函数原型的后半部,lhs 与 rhs 才是有意义的。针对此问题,C++(www.cppentry.com)11 引进一种新的函数定义与声明的语法:
template< typename LHS, typename RHS>
auto AddingFunc(const LHS &lhs, const RHS &rhs) -> decltype(lhs+rhs) {return lhs + rhs;}
这种语法也能套用到一般的函数定义与声明:
struct SomeStruct
{
auto FuncName(int x, int y) -> int;
};
[] SomeStruct::FuncName(int x, int y) -> int
{
return x + y;
}
关键字 auto 的使用与其在自动类型推导代表不同的意义。
对象建构的改良
在标准C++(www.cppentry.com)中,建构式不能调用其它的建构式;每个建构式必须自己初始化所有的成员或是调用一个共用的成员函数。基类的建构式不能够直接作为派生类的建构式;就算基类的建构式已经足够,每个衍伸的类型仍必须实做自己的建构式。类型中non-constant的数据成员不能够在声明的地方被初始化,它们只能在建构式中被初始化。 C++(www.cppentry.com)11将会提供这些问题的解决方案。
C++(www.cppentry.com)11允许建构式调用其他建构式,这种做法称作委托或转接(delegation)。 仅仅只需要加入少量的代码,就能让数个建构式之间达成功能复用(reuse)。 Java以及C#都有提供这种功能。C++(www.cppentry.com)11 语法如下:
class SomeType {
int number;
string name;
SomeType( int i, string& s ) : number(i), name(s){}
public:
SomeType( ) : SomeType( 0, "invalid" ){}
SomeType( int i ) : SomeType( i, "guest" ){}
SomeType( string& s ) : SomeType( 1, s ){ PostInit(); }
};
C++(www.cppentry.com)03中,建构式运行退出代表对象建构完成; 而允许使用转接建构式的 C++(www.cppentry.com)11 则是以"任何"一个建构式退出代表建构完成。 使用转接的建构式,函数本体中的代码将于被转接的建构式完成后继续运行(如上例的 PostInit()
)。 若基底类型使用了转接建构式,则派生类的建构式会在"所有"基底类型的建构式都完成后, 才会开始运行。C++(www.cppentry.com)11 允许派生类手动继承基底类型的建构式, 编译器可以使用基底类型的建构式完成派生类的建构。 而将基类的建构式带入派生类的动作, 无法选择性地部分带入, 要不就是继承基类全部的建构式,要不就是一个都不继承(不手动带入)。 此外,若牵涉到多重继承,从多个基底类型继承而来的建构式不可以有相同的函数签名(signature)。 而派生类的新加入的建构式也不可以和继承而来的基底建构式有相同的函数签名,因为这相当于重复声明。
语法如下:
class BaseClass
{
public:
BaseClass(int iValue);
};
class DerivedClass : public BaseClass
{
public:
using BaseClass::BaseClass;
};
此语法等同于 DerivedClass 声明一个DerivedClass(int)
的建构式。 同时也因为 DerivedClass 有了一个继承而来的建构式,所以不会有默认建构式。另一方面,C++(www.cppentry.com)11可以使用以下的语法完成成员初始化:
class SomeClass
{
public:
SomeClass() {}
explicit SomeClass(int iNewValue) : iValue(iNewValue) {}
private:
int iValue = 5;
};
若是建构式中没有设置iValue
的初始值,则会采用类定义中的成员初始化,令iValue初值为5。在上例中,无参数版本的建构式,iValue便采用默认所定义的值; 而带有一个整数参数的建构式则会以指定的值完成初始化。成员初始化除了上例中的赋值形式(使用"=")外,也可以采用建构式以及统一形的初始化(uniform initialization,使用"{}")。
显式虚函数重载
在 C++(www.cppentry.com) 里,在子类中容易意外的重载虚函数。举例来说:
struct Base {
virtual void some_func();
};
struct Derived : Base {
void some_func();
};
Derived::some_func
的真实意图为何 程序员真的试图重载该虚函数,或这只是意外 这也可能是 base 的维护者在其中加入了一个与 Derived::some_func 同名且拥有相同签名的虚函数。另一个可能的状况是,当基类中的虚函数的签名被改变,子类中拥有旧签名的函数就不再重载该虚函数。因此,如果程序员忘记修改所有子类,运行期将不会正确调用到该虚函数正确的实现。
C++(www.cppentry.com)11 将加入支持用来防止上述情形产生,并在编译期而非运行期捕获此类错误。为保持向后兼容,此功能将是选择性的。其语法如下:
class B {
typedef B self;
virtual void some_func1();
virtual void some_func2(float);
virtual void some_func3() const;
virtual long some_func4(int);
virtual void f();
virtual void h(int);
void j(int);
void k(int);
};
class D [[base_check]] : public B {
using B::j;
void sone_func1 [[override]] (); // 錯誤格式: 錯誤的函式名稱
void some_func2 [[override]] (double); // 錯誤格式: 錯誤的參數型別
void some_func3 [[override]] (); // 錯誤格式: 沒有加上 cv-qualification
int some_func4 [[override]] (int); // 錯誤格式: 不符合 B::some_func4 的回返型別
virtual void f [[override]] (); // 正確: 重載 B::f
virtual void g(long); // 引入新的虛函式
void h(int); // 錯誤格式: 重載為加上 [[override]]
virtual void h(double); // 錯誤格式: 新的虛函式隱藏了 void h(int)
virtual void h [[hiding]] (char *); // 正確, 新的虛函式隱藏了 void h(int)
virtual int j( double ); // 正確, 使用 using B::j 防止隱藏
int k( double ); // 錯誤格式: name hiding and no using declaration
double k [[hiding]] ( char * ); // 正確, hiding is clearly indicated
double m [[hiding]] ( char * ); // 錯誤格式, hiding is requested, but not present
typedef D self; // ill-formed, new type definition hides the definition in B
};
一个 class/struct 若带有 [[base_check]]
属性,则意谓著任何隐式重载将会导致编译期错误。所有的重载都必须加上 [[override]] 属性。[[hiding]] 意谓著新函数隐藏了基类的函数。
空指针
早在 1972 年,C语言诞生的初期,常数 0 带有常数及空指针的双重身分。 C 使用 preprocessor macro NULL
表示空指针, 让 NULL 及 0 分别代表空指针及常数 0。 NULL 可被定义为 ((void*)0) 或是 0。C++(www.cppentry.com) 并不采用 C 的规则,不允许将 void*
隐式转换为其他类型的指针。 为了使代码 char* c = NULL; 能通过编译,NULL 只能定义为 0。 这样的决定使得函数重载无法区分代码的语义:void foo(char *);
void foo(int);
C++(www.cppentry.com) 建议 NULL
应当定义为 0,所以foo(NULL); 将会调用 foo(int), 这并不是程序员想要的行为,也违反了代码的直观性。0 的歧义在此处造成困扰。C++(www.cppentry.com)11 引入了新的关键字来代表空指针常数:nullptr
,将空指针和整数 0 的概念拆开。 nullptr 的类型为nullptr_t,能隐式转换为任何指针或是成员指针的类型,也能和它们进行相等或不等的比较。 而nullptr不能隐式转换为整数,也不能和整数做比较。为了向下兼容,0
仍可代表空指针常数。char* pc = nullptr; // OK
int * pi = nullptr; // OK
int i = nullptr; // error
foo(nullptr); // 呼叫 foo(char *)
强类型枚举
在标准C++(www.cppentry.com)中,枚举类型不是类型安全的。枚举类型被视为整数,这使得两种不同的枚举类型之间可以进行比较。C++(www.cppentry.com)03 唯一提供的安全机制是一个整数或一个枚举型值不能隐式转换到另一个枚举别型。 此外,枚举所使用整数类型及其大小都由实现方法定义,皆无法明确指定。 最后,枚举的名称全数暴露于一般范围中,因此两个不同的枚举,不可以有相同的枚举名。 (好比 enum Side{ Right, Left };
和 enum Thing{ Wrong, Right }; 不能一起使用。)C++(www.cppentry.com)11 引进了一种特别的 "枚举类",可以避免上述的问题。使用 enum class
的语法来声明:enum class Enumeration
{
Val1,
Val2,
Val3 = 100,
Val4 /* = 101 */,
};
此种枚举为类型安全的。枚举类型不能隐式地转换为整数;也无法与整数数值做比较。 (表示式 Enumeration::Val4 == 101
会触发编译期错误)。枚举类型所使用类型必须显式指定。在上面的示例中,使用的是默认类型 int
,但也可以指定其他类型:enum class Enum2 : unsigned int {Val1, Val2};
枚举类型的语汇范围(scoping)定义于枚举类型的名称范围中。 使用枚举类型的枚举名时,必须明确指定其所属范围。 由前述枚举类型 Enum2 为例,Enum2::Val1
是有意义的表示法, 而单独的 Val1 则否。此外,C++(www.cppentry.com)11 允许为传统的枚举指定使用类型:
enum Enum3 : unsigned long {Val1 = 1, Val2};
枚举名 Val1
定义于 Enum3 的枚举范围中(Enum3::Val1),但为了兼容性, Val1 仍然可以于一般的范围中单独使用。在 C++(www.cppentry.com)11 中,枚举类型的前置声明 (forward declaration) 也是可行的,只要使用可指定类型的新式枚举即可。 之前的 C++(www.cppentry.com) 无法写出枚举的前置声明,是由于无法确定枚举变量所占的空间大小, C++(www.cppentry.com)11 解决了这个问题:
enum Enum1; // 不合法的 C++(www.cppentry.com) 與 C++(www.cppentry.com)11; 無法判別大小
enum Enum2 : unsigned int; // 合法的 C++(www.cppentry.com)11
enum class Enum3; // 合法的 C++(www.cppentry.com)11,列舉類別使用預設型別 int
enum class Enum4: unsigned int; // 合法的 C++(www.cppentry.com)11
enum Enum2 : unsigned short; // 不合法的 C++(www.cppentry.com)11,Enum2 已被聲明為 unsigned int
角括号
标准 C++(www.cppentry.com) 的剖析器一律将 ">>" 视为右移操作符。 但在样板定义式中,绝大多数的场合其实都代表两个连续右角括号。 为了避免剖析器误判,撰码时不能把右角括号连着写。
C++(www.cppentry.com)11 变更了剖析器的解读规则;当遇到连续的右角括号时,优先解析右角括号为样板引数的退出符号。 如果解读过程中出现普通括号("(" 与 ")"),这条规则产生变化:
template<bool bTest> SomeType;
std::vector<SomeType<1>2>> x1; // 解讀為 std::vector of "SomeType<true> 2>",
// 非法的表示式, 整數 1 被轉換為 bool 型別 true
std::vector<SomeType<(1>2)>> x1; // 解讀為 std::vector of "SomeType<false>",
// 合法的 C++(www.cppentry.com)11 表示式, (1>2) 被轉換為 bool 型別 false
显式类型转换子
C++(www.cppentry.com) 为了避免用户自定的单引数建构式被当成隐式类型转换子,引入了关键字 explicit
修饰字。 但是,在编译器对对象调用隐式类型转换的部分,则没有任何着墨。 比方说,一个 smart pointer 类型具有一个operator bool(), 被定义成若该 smart pointer 保管任何资源或指针,则传回 true,反之传回 false。 遇到这样的代码时:if(smart_ptr_variable),编译器可以借由 operator bool() 隐式转换成布林值, 和测试原生指针的方法一样。 但是这类隐式转换同样也会发生在非预期之处。由于 C++(www.cppentry.com) 的bool 类型也是算数类型,能隐式换为整数甚至是浮点数。 拿对象转换出的布林值做布林运算以外的数学运算,往往不是程序员想要的。在 C++(www.cppentry.com)11 中,关键字 explicit
修饰符也能套用到类型转换子上。如同建构式一样,它能避免类型转换子被隐式转换调用。但 C++(www.cppentry.com)11 特别针对布林值转换提出规范,在 if 条件式,循环,逻辑运算等需要布林值的地方,编译器能为符合规范的表示式调用用户自定的布林类型转换子。
模板的别名
在进入这个主题之前,各位应该先弄清楚“模板”和“类型”本质上的不同。class template (类型模板,是模板)是用来产生 template class (模板类型,是类型)。
在标准 C++(www.cppentry.com),typedef
可定义模板类型一个新的类型名称,但是不能够使用 typedef 来定义模板的别名。举例来说:template< typename first, typename second, int third>
class SomeType;
template< typename second>
typedef SomeType<OtherType, second, 5> TypedefName; // 在C++(www.cppentry.com)是不合法的
这不能够通过编译。
为了定义模板的别名,C++(www.cppentry.com)11 将会增加以下的语法:
template< typename first, typename second, int third>
class SomeType;
template< typename second>
using TypedefName = SomeType<OtherType, second, 5>;
using
也能在 C++(www.cppentry.com)11 中定义一般类型的别名,等同 typedef:typedef void (*PFD)(double); // 傳統語法
using PFD = void (*)(double); // 新增語法
无限制的unions
在标准 C++(www.cppentry.com) 中,并非任意的类型都能做为 union 的成员。比方说,带有 non-trivial 构造函数的类型就不能是 union 的成员。在新的标准里,移除了所有对 union 的使用限制,除了其成员仍然不能是引用类型。 这一改变使得 union 更强大,更有用,也易于使用。[1]
以下为 C++(www.cppentry.com)11 中 union 使用的简单样例:
struct point
{
point() {}
point(int x, int y): x_(x), y_(y) {}
int x_, y_;
};
union
{
int z;
double w;
point p; // 不合法的 C++(www.cppentry.com); point 有一 non-trivial 建構式
// 合法的 C++(www.cppentry.com)11
};
这一改变仅放宽 union 的使用限制,不会影响既有的旧代码。