我是在一个帖子上摘抄的大神语录。。。感谢supermegaboy大神,给了详尽的解释
下文是一篇转载的Wikipedia的译文,从语言和库双方面概述了C++0x。
右值引用与转移语义
在标准C++语言中,暂时量(术语为右值,因其出如今赋值表达式的右边)能够被传给函数,但仅仅能被接受为const &类型。这样函数便无法区分传给const &的是真实的右值还是常规变量。并且,因为类型为const &,函数也无法改变所传对象的值。
C++0x将添加一种名为右值引用的新的引用类型,记作typename &&。这样的类型能够被接受为非const值,从而同意改变其值。这样的改变将同意某些对象创建转移语义。
比方,一个std::vector,就其内部实现而言,是一个C式数组的封装。假设须要创建vector暂时量或者从函数中返回vector,那就仅仅能通过创建一个新的vector并拷
贝全部存于右值中的数据来存储数据。之后这个暂时的vector则会被销毁,同一时候删除其包括的数据。
有了右值引用,一个參数为指向某个vector的右值引用的std::vector的转移构造器就行简单地将该右值中C式数组的指针拷贝到新的vector,然后将该右值清
空。这里没有数组拷贝,而且销毁被清空的右值也不会销毁保存数据的内存。返回vector的函数如今仅仅须要返回一个std::vector<>&&。假设vector没有转移构造
器,那么结果会像曾经一样:用std::vector<> &參数调用它的拷贝构造器。假设vector确实具有转移构造器,那么转移构造器就会被调用,从而避免大量的内存分配。
考虑到安全因素,具名变量即使被声明为右值类型也不会被当作右值。如需把它当作右值,须使用库函数std::move()。
bool is_r_val(int &&)
{
return tr;
}
bool is_r_val(const int &)
{
return false;
}
void test(int &&i)
{
is_r_val(i); // false
is_r_val(std::move(i)); // tr
}
出于右值引用定义的本质特征以及某些对左值引用(常规引用)定义的改动,如今右值引用同意程序猿提供函数參数的完美转发。当与模板变參相结合时,这样的能力能够同意函数模板完美地将參数转发给接受那些參数的其它函数。这在转发构造器參数时尤为实用:能够创建一些能自己主动调用具有对应參数构造器的工厂函数。
C++语言一直具有常量表达式的概念。这些诸如3+4之类的表达式总是产生同样的结果且不具备副作用。常量表达式给编译器带来了优化的可能,而编译器也常常在编译期运行此类表达式并将结果存放在程序中。此外,C++语言规范中有一些地方须要使用常量表达式。定义数组须要常量表达式,而枚举值也必须是常量表达式。
然而,每当碰到函数调用或对象构造,常量表达式便不再有效。所以简单例如以下例便不合法:
int GetFive()
{
return 5;
}
int some_val[GetFive() + 5]; //create an array of 10 integers. illegal C++
这段代码在C++中不合法,由于GetFive() + 5不是一个常量表达式。编译器无从知晓GetFive在执行期是否产生常量。理论上,这个函数可能会影响某个全局变量,或者调用其它执行期产生很量的函数。
C++0x将引入constexprkeyword,此keyword将使用户能保证某个函数或构造器在编译期产生常量。上例可被改写例如以下:
constexpr int GetFive()
{
return 5;
}
int some_val[GetFive() + 5]; //create an array of 10 integers. legal C++0x
这段代码将使编译器理解并确认GetFive是个编译期常量。
在函数上使用constexpr将对函数功能施加严格的限制。首先,函数必须返回非void类型。其次,函数体必须具有"return /expr/"的形式。第三,expr在參数替换后必须是常量表达式。该常量表达式仅仅能调用其它定义为constexpr的函数,仅仅能使用其它常量表达式数据变量。第四,常量表达式中一切形式的递归均被禁止。最后,这样的带constexpr的函数在编译单元中必须先定义后调用。
变量也可被定义为常量表达式值。
constexpr do le forceOfGravity = 9.8;
constexpr do le moonGravity = forceOfGravity / 6;
常量表达式数据变量隐含为常量。它们仅仅能存放常量表达式或常量表达式构造器的结果。
为了从用户自己定义类型中构建常量表达式数据值,构造器在声明时可带constexpr。同常量表达式函数一样,在编译单元中常量表达式构造器也必须先定义后使用。常量表达式构造器函数体必须为空,并且它必须用常量表达式构造其成员。这样的类型的析构器必须是平庸的。
由常量表达式拷贝构造的类型也必须被定义为constexpr,以使它们能从常量表达式函数中作为值被返回。类的不论什么成员函数,包含拷贝构造器和操作符重载,都能被声明为constexpr,仅仅要它们符合常量表达式函数的定义。这就同意编译器在编译期不仅能拷贝类对象,也能对事实上施其它操作。
常量表达式函数或构造器能够用非constexpr參数来调用。就如同一个constexpr整数常量能够被赋给一个非constexpr变量一样,constexpr函数也可用非constexpr參数来调用,而且其结果也可存放在非constexpr变量中。此keyword仅仅是提供了在一个表达式的所有成员均为constexpr时其结果为编译期常量的可能性。
在标准C++语言中,要让结构成为POD类型必须满足某几条规则。有充分理由让一大堆类型满足这些规则(定义);仅仅要满足这些规则,结构的实现将产生兼容于C的对象布局。然而,在C++03中这些规则过于严格。
C++0x将放松某些关于POD的限制规则。
假设一个类或结构是平庸的,具有标准布局的,且不包括不论什么非POD的非静态成员,那么它就被认定是POD。平庸的类或结构定义例如以下:
1.具有一个平庸的缺省构造器。(能够使用缺省构造器语法,如 SomeConstr tor() = default;).
2.具有一个平庸的拷贝构造器。(能够使用缺省构造器语法).
3.具有一个平庸的拷贝赋值运算符。(能够使用缺省语法)
4.具有一个非虚且平庸的析构器。
一个具有标准布局的类或结构被定义例如以下:
1.全部非静态数据成员均为标准布局类型。
2.全部非静态成员的訪问权限(p lic, private, protected) 均同样。
3.没有虚函数。
4.没有虚基类。
5.全部基类均为标准布局类型。
6.没有不论什么基类的类型与类中第一个非静态成员同样。
7.要么所有基类都没有非静态数据成员,要么最下层的子类没有非静态数据成
员且最多仅仅有一个基类有非静态数据成员。总之继承树中最多仅仅能有一个类有非静
态数据成员。全部非静态数据成员必须都是标准布局类型。
在标准C++语言中,假设在某一个编译单元中编译器碰到一个參数全然指定的模
板,它就必须具现化该模板。这样的做法可能大大延长编译时间,尤其在很多编译单
元使用相同的參数具现化该模板时。
C++0x将引入外部模板的概念。C++已经拥有了迫使编译器在某一地点具现化模板的语法:
template class std::vector<MyClass>;
C++所缺乏的是防止编译器具现化某个模板的能力。C++0x仅仅是简单地将语法扩展为:
extern template class std::vector<MyClass>;
这段代码将告诉编译器不要在这个编译单元具现化此模板。
标准C++语言从C语言中借入了初始化列表概念。依据这一概念,结构或数组能够通过给定一串依照结构中成员定义的次序排列的參数来创建。初始化列表能够递归创建,因此结构数组或包括其它结构的结构也能使用初始化列表。这对于静态列表或用某些特定值初始化结构而言很实用。C++语言中存在能让对象初始化的构造器特性。但构造器特性本身并不能代替初始化列表的全部功能。标准C++同意类和结构使用初始化列表,但它们必须满足POD的定义。非POD的类不能使用初始化列表,一些C++式的容器如std::vector和boost::array也不行。
C++0x将把初始化列表绑定为一种名为std::initializer_list的类型。这将同意构造器及其它函数接受初始化列表作为其參数。比方:
class SeqnceClass
{
p lic:
SeqnceClass(std::initializer_list < int >list);
};
这段代码将同意SeqnceClass用一串整数构造,例如以下所看到的:
SeqnceClass someVar = {1, 4, 5, 6};
这样的构造器是一种特殊类型的构造器,名为初始化列表构造器。具有这样的构造器的类在统一的初始化形式中将被特殊对待。
std::initializer_list<>类在C++0x标准库中将成为一等公民。可是这个类的对象仅仅能通过使用{}语法由C++0x编译器静态构建并初始化。列表一旦构建就可以被拷贝,虽然仅仅是引用拷贝。初始化列表是常量,一旦构建,组成列表的成员以及其成员所包括的数据便无法改变。
因为初始化列表是一种真实的类型,因此在类构造器之外的地方也能使用。常规函数也可接受初始化列表作为其參数。比方:
void FunctionName(std::initializer_list<float> list);
FunctionName({1.0f, -3.45f, -0.4f});
标准C++在类型初始化中存在一些问题。语言中存在几种类型初始化方式,但替换使用的话产生的结果不尽同样。传统的构造语法看起来更像函数声明。必须採取措施以使编译器不把对象构造误觉得函数声明。仅仅有集合类型和POD类型能用集合初始化器初始化(用SomeType var = {/*st?*/};).
C++0x将提供一种能作用于不论什么对象的全然统一的类型初始化形式。这样的形式对初始化列表语法作了扩展:
str t BasicStr t
{
int x;
float y;
};
str t AltStr t
{
AltStr t(int _x, float _y) : x(_x), y(_y) {}
private:
int x;
float y;
};
BasicStr t var1{5, 3.2f};
AltStr t var2{2, 4.3f};
var1的初始化的运作方式就如同一个C式的初始化列表。每一个p lic变量都将用初始化列表中的值初始化。假设须要,隐式类型转化将被使用,而且假设没有隐式类型转化可供使用,编译器将报告编译失败。
var2的初始化仅仅是简单地调用构造器。
统一的初始化对象构造将消除在某些情况下指定类型的须要:
str t IdString
{
std::string name;
int identifier;
};
IdString var3{"SomeName", 4};
这样的语法会自己主动使用const char *调用std::string进行初始化。程序猿也能够使用以下的代码:
IdString GetString()
{
return {"SomeName", 4}; //Note the lack of explicit type.
}
统一的初始化形式不会代替构造器语法。某些情况下仍然须要构造器语法。假设一个类具有初始化列表构造器(TypeName(initializer_list);),,那么仅仅要初始化列表符合该构造器的类型,初始化列表构造将优先于其它构造形式。C++0x版本号的std::vector将拥有匹配与模板參数的初始化列表构造器。这就意味着以下这段代码:
std::vector<int> theVec{4};
这段代码将调用初始化列表构造器,而不会调用std::vector中接受单个长度參数并创建对应长度的vector的构造器。为了调用后一个构造器,用户须要直接使用标准构造器语法。
类型推定
在标准的C++和C语言中,变量在使用时必须明白指定其类型。然而,随着模板类型及模板元编程的到来,表述某些定义完善的函数的返回值的类型变得不那么easy了。由此,在函数中存储中间值也变得困难了,用户有可能须要了解某个模板元编程库的内部结构才行。
C++0x将通过两种方式来缓解这些困难。首先,带有明白初始化的变量定义将能够使用autokeyword。这样的初始化将创建与初始化器类型同样的变量。
auto someStrangeCallableType = boost::bind(&SomeFunction, _2, _1, someObject);
auto otherVariable = 5;
someStrangeCallableType的类型将等同于不论什么由boost::bind所返回的适合于这些特定參数的模板函数的类型。编译器非常easy知道其类型,用户则不然。
otherVariable的类型也定义完善,但用户更easy推定其类型。该变量是整形,也就是整型常量的类型。
另外,keyworddecltype可用于在编译期确定某个表达式的类型。比方:
int someInt;
decltype(someInt) otherIntegerVariable = 5;
这样的使用方法相对于auto可能更有效,由于auto变量的类型仅仅有编译器才知道。并且,对于那些大量使用操作符重载及特化类型的代码,使用decltype来推导表达式的类
型也非常实用。
auto在降低代码冗余性方面也非常实用。比方,写以下这段代码时:
for(vector<int>::const_iterator itr = myvec.begin();
itr != myvec.end(); ++itr)
程序猿能够使用以下这样的更短的形式:
for (auto itr = myvec.begin(); itr != myvec.end(); ++itr)
当程序猿在開始使用嵌套容器时,这两段代码的差别将更加明显,虽然在这样的情况下使用typedef也是一种降低代码的好方法。
C++库Boost定义了几个区间概念。区间代表了与容器相类似的列表中两点之间的可控列表。已序容器是区间的超集。已序容器中的两个迭代器也能定义一个区间。这些概念和算法都将被融入C++0x的标准库中。然而,C++0x还将提供一种专用的语言设施来运用区间概念。
for语句将使区间概念上的循环更easy:
int my_array[5] = { 1, 2, 3, 4, 5 };
for(int &x:my_array)
{
x *= 2;
}
新的for循环的第一部分定义了用于在区间上循环的变量。和普通for循环中声明的变量一样,该变量的作用域也仅限于循环之内。置于":"之后的第二部分则表示将进行循环的区间。在这样的情况下,存在一个约束映射能够将C式数组转化为区间。进行循环的区间还能够是std::vector,或不论什么符合区间概念的对象。
在标准C++语言中,尤其在使用诸如sort和find之类的标准库算法函数时,用户总是希望在算法函数调用的触发点附近定义谓词函数。在这一方面语言中仅仅有一种机制可供利用:在函数中定义类。通常这样的做法既啰嗦又笨重。另外,标准C++语言不同意在函数中定义的类充当模板參数,所以这样的做法行不通。
显而易见,解决方式在于同意定义lambda表达式和 lambda函数。C++0x将同意定义lambda函数。
lambda函数能够定义例如以下:
[](int x, int y) { return x + y }
此无名函数的返回值类型为decltype(x+y)。仅仅有lambda函数的形式为"return/expression/"时,返回值类型才干省略。因此这样的lambda函数内部仅仅能有一句语句。
返回值类型也可像下例那样明白指定。一个更为复杂的样例:
为栈变量生成的闭包变量也能够不用引用操作符/&/定义,这样的情况下lambda函数
将拷贝其值。这样的做法将促使用户明白声明其意图:是引用栈变量还是拷贝栈变
量。引用栈变量可能会产生危急。假设某个lambda函数将在所创建的作用域之外被
引用(比方将此lambda函数存放在std::function(C++0x标准)对象中能够做到这
一点),那么用户必须保证该lambda函数没有引用不论什么栈变量。
对于那些能够保证仅仅在其所创建的作用域内被运行的lambda函数,使用栈变量无须
通过显式引用:
std::vector<int> someList;
int total = 0;
std::for_each(someList.begin(), someList.end(), [&](int x) {
total += x
});
这样的lambda函数的详细内部实现可能会有所不同,但能够预期这样的lambda函数可能
不会保存全部栈变量的引用而是会保存函数创建时的栈指针。
假设不用[&]而用[=],那么全部被引用的变量都将被拷贝,从而同意lambda函数在
原有变量生命期结束后仍然可以被使用。
缺省指示符还能和參数列表结合使用。比方,假设用户希望仅仅拷贝当中一个变量的
值,而对其它变量使用引用,则可使用以下的代码:
int total = 0;
int val = 5;
[&, val](int x) { total += (x * val) };
这段代码将导致total被存为引用,而val则会被存为拷贝。
假设一个lambda函数由一个类的某个成员函数定义,那么此lambda函数便被认定为
该类的友元。这样的lambda函数能够使用属于该类类型的对象的引用并訪问其内部成员。
[](SomeType *typePtr) { typePtr->SomePrivateMemberFunction() };
仅仅有当lambda函数在SomeType的某个成员函数中创建时这段代码才干工作。
对于指向当前成员函数所隶属对象的this指针,其处理有些特殊:必须在lambda函
数中明白指定。
[this]() { this->SomePrivateMemberFunction() };
使用[&] 或 [=]形式将使this自己主动可用。
Lambda函数是一些类型取决于编译器的函数对象。它们的类型仅仅对编译器开放。如
果用户希望把lambda函数当作參数,那么要么參数对应类型为模板,要么创建一个
std::function用于保存lambda函数。使用autokeyword则能够将lambda函数保存在
局部变量中。
auto myLambdaFunc = [this]() {
this->SomePrivateMemberFunction()
};
然而,假设lambda函数的全部闭包变量均为引用,或者lambda函数根本没有闭包变
量,那么所产生的函数对象将具有一种特殊类
型:std::reference_closure。当中R(P)是带返回值的函数签名。这样做的
理由在于期望此种类型的效率能好于使用std::function。
std::reference_closure<void()> myLambdaFunc = [this]() {
this->SomePrivateMemberFunction()
};
myLambdaFunc();
新增的函数语法
标准C语言的函数声明语法对于C语言的特性集来说是完美无缺的。因为C++语言演
化自C语言,C++语言保留了相关的基本的语法并在须要时进行扩充。然而,当C++变
得更为复杂时,这样的语法也暴露了一些局限性,尤其是在模板函数声明中。比方,
下面代码在C++03中不合法:
template< typename LHS, typename RHS>
Ret AddingFunc(const LHS &lhs, const RHS &rhs)
{
return lhs + rhs;
}
类型Ret为不论什么LHS和RHS相加所产生的类型。即使有了前面所讲述的C++0x的
decltype功能,仍然不行:
template< typename LHS, typename RHS>
decltype(lhs+rhs) AddingFunc(const LHS &lhs, const RHS &rhs)
{
return lhs + rhs;
}
这一段并不是合法的C++代码,由于lhs和rhs尚没有定义,仅仅有在词法分析器分析出函
数原型的其余部分之后这两者才干成为有效的标识符。
为解决这一问题,C++0x将引入一种新型的函数定义和声明的语法:
template < typename LHS, typename RHS >
auto AddingFunc(const LHS & lhs, const RHS & rhs)->decltype(lhs + rhs)
{
return lhs + rhs;
}
这一语法也能用于更为寻常的函数声明和定义中:
str t SomeStr t
{
auto FuncName(int x, int y)->int;
};
auto SomeStr t::FuncName(int x, int y)->int
{
return x + y;
}
约束
在C++语言中,模板类和模板函数必须对它们所接受的类型施加某些限制。比
如,STL容器要求容器中的类型必须能够赋值。与类继承所展示的动多态(不论什么能
接受Foo&类型对象作为參数的函数也能传入Foo的子类型)有所不同,不论什么类仅仅要
支持某个模板所使用的操作,它就能被用于该模板。在函数传參数的情况下,參数
所必须满足的需求是清晰的(必须是Foo的子类型),而模板的场合下,对象所需
满足的接口则是隐含在模板实现其中的。约束则提供了一种将模板參数所必需满足
的接口代码化的机制。
引入约束的最初动因在于改进编译错误信息的质量。假设程序猿试图使用一种不能
提供某个模板所需接口的类型,那么编译器将产生错误信息。然而,这些错误信息
通常难以理解,尤其对于新手而言。首先,错误信息中的模板參数通常被完整拼写
出来,这将导致异常庞大的错误信息。在某些编译器上,简单的错误会产生好几K
的错误信息。其次,这些错误信息通常不会指向错误的实际发生地点。比方,假设
程序猿试图创建一个其成员为不具备拷贝构造器对象的vector,首先出现的错误信
息差点儿总是指向vector类中试图拷贝构造其成员的那段代码。程序猿必须具备足够
的经验和能力才干推断出实际的错误在于对应类型无法全然满足vector所须要的接口。
在试图解决此问题的过程中,C++0x为语言加入了约束这一特性。与OOP使用基类来
限制类型的功能相似,约束是一种限制类型接口的具名结构。而与OOP所不同的
是,约束定义并不是总是与传入模板的參数类型明白相关,但它总是与模板定义相关:
template < LessThanComparable T >
const T & min(const T & x, const T & y)
{
return y < x ? y : x;
}
这里没实用/class /或/ typename/将模板參数指定为随意类型,而是使用了
/LessThanComparable/这个之前定义的约束。假设某个传入/min/模板參数的类型
不符合/LessThanComparable/约束的定义,那么编译器将报告编译错误,告诉用户
用来具现化该模板的类型不符合/LessThanComparable/约束。
以下是一个更一般化的约束形式:
template<typename T> requires LessThanComparable<T>
const T& min(const T &x, const T &y)
{
return y < x ? y : x;
}
keyword/requires/之后为一串约束的声明。它能够被用于表述涉及多个类型的约
束。此外,假设用户希望当类型匹配该约束时不要使用某个特定模板,也能够用
/requires !LessThanComparable/。能够像模板特化那样使用这样的机制。一个
通用模板可能通过显式禁用一些特性丰富的约束来处理具有较少特性的类型。而这
些约束则可通过特化利用某些特性来取得更高的效率并实现很多其它的功能。
约束定义例如以下:
auto concept LessThanComparable < typename T >
{
bool operator<(T, T);
}
这个样例中的keyword/auto/意味着不论什么类型仅仅要支持约束中所指定的操作便被认定
支持该约束。假设不使用/auto/keyword,为声明某个类型支持该约束就必须对该类
型使用约束映射。
该约束声明不论什么类型仅仅要定义了接受两个參数并返回bool型的<操作符就被觉得是
/LessThanComparable/。该操作符不一定是一个自由函数,它也能够是T类型的成
员函数。
约束也能够涉及多个类型。比方,约束能表示一个类型能够转换为还有一个类型:
auto concept Convertible < typename T, typename U >
{
operator U(const T &);
}
为了在模板中使用这个约束,模板必须使用一种更为一般化的形式:
template < typename U, typename T >
requires Convertible < T, U > U convert(const T & t)
{
return t;
}
约束能够组合运用。比方,给定一个名为/Regular/的约束
concept InputIterator < typename Iter, typename Val >
{
requires Regular < Iter >;
Val operator*(const Iter &);
Iter & operator++(Iter &);
Iter operator++(Iter &, int);
}
/InputIterator/约束的第一个模板參数必须符合/Regular/约束。
与继承相似,约束也可派生自还有一约束。与类继承相似,满足派生约束全部限制条
件的类型必须满足基本约束的全部限制条件。约束派生定义形同类派生:
concept ForwardIterator<typename Iter, typename Val> :
InputIterator<Iter, Val>
{
//Add other requirements here.
}
类型名能够与约束相关。这将施加一些限制条件:在使用这些约束的模板中,这些
类型名可供使用:
concept InputIterator < typename Iter >
{
typename val_type;
typename reference;
typename pointer;
typename difference_type;
requires Regular < Iter >;
requires Convertible < reference, val_type >;
reference operator*(const Iter &); // dereference
Iter & operator++(Iter &); // pre-increment
Iter operator++(Iter &, int); // post-increment
// ...
}
约束映射同意某些类型被显式绑定到某个约束。如有可能,约束映射也同意在不改
变类型定义的前提下让该类型採用某个约束的语法。比方下例:
concept_map InputIterator < char *>
{
typedef char val_type;
typedef char &reference;
typedef char *pointer;
typedef std::ptrdiff_t difference_type;
};
这个约束映射填补了当/InputIterator/映射作用于/char*/类型时所须要的类型名。
为添加灵活性,约束映射本身也能被模板化。上例能够被延伸至全部指针类型:
template < typename T > concept_map InputIterator < T * >
{
typedef T val_type;
typedef T & reference;
typedef T *pointer;
typedef std::ptrdiff_t difference_type;
};
此外,约束映射也可充当迷你类型,此时它会包括函数定义以及其它与类相关的结
构设施:
concept Stack < typename X >
{
typename val_type;
void push(X &, const val_type &);
void pop(X &);
val_type top(const X &);
bool empty(const X &);
};
template < typename T > concept_map Stack < std::vector < T > >
{
typedef T val_type;
void push(std::vector < T > &v, const T & x)
{
v.push_back(x);
}
void pop(std::vector < T > &v)
{
v.pop_back();
}
T top(const std::vector < T > &v)
{
return v.back();
}
bool empty(const std::vector < T > &v)
{
return v.empty();
}
};
这个约束映射将同意不论什么接受实现了/Stack/约束的类型的模板也接受
/std::vector/,同一时候将全部函数调用映射为对/std::vector/的调用。终于,这样的
做法将同意一个已经存在的对象在不改变其定义的前提下,转换其接口并为模板函
数所利用。
最后须要指出的是,某些限制条件能够通过静态断言来检測。这样的手段能够用来检
測那些模板须要但却面向其它方面问题的限制条件。
对象构建方面的改进
在标准C++语言中,构造器不能调用其它构造器。每一个构造器要么独自构建类的所
有成员要么调用某个公共成员函数。基类的构造器不能直接暴露给派生类:即便基
类的构造器更合适,子类也必须实现自己的构造器。类的非静态数据成员不能在其
声明的场所初始化,它们仅仅能在构造器中初始化。
C++0x将为全部这些问题提供解决方式。
C++0x将同意构造器调用其它伙伴构造器(被称为托付)。如此,仅仅需加入少量代
码,构造器便能利用其它构造器的行为。另外一些语言,比方Java
和C#
,同意
这样做。
语法例如以下:
class SomeType
{
int number;
p lic:
SomeType(int newNumber):number(newNumber)
{
}
SomeType():SomeType(42)
{
}
};
这就产生了一个问题:C++03觉得一个对象在其构造器运行完毕时才干构建完毕,
而C++0x则觉得一个对象在不论什么构造器运行完毕时都将构建完毕。因为多个构造器
被同意运行,这将导致每一个托付构造器都可能在一个已经构造完毕的对象上运行操
作。派生类构造器将在基类的全部托付构造器运行完成后运行。
关于基类构造器,C++0x将同意一个类指定须要继承的基类构造器。这意味着C++0x
编译器将产生代码用于类继承,即将子类构造转发为基类构造。注意这是一个要么
所有要么没有的特性:或者所有构造器被转发,或者没有构造器被转发。另外注意
在多重继承的情况下有些限制,比方类构造器不能继承来自两个类的具有同样签名
的构造器。同一时候子类构造器的签名也不能与用来继承的基类构造器相匹配。
对应语法例如以下:
class BaseClass
{
p lic:
BaseClass(int iVal);
};
class DerivedClass:p lic BaseClass
{
p lic:
using default BaseClass;
};
关于成员初始化,C++0x将同意下面语法:
class SomeClass
{
p lic:
SomeClass()
{
}
explicit SomeClass(int iNewVal):iVal(iNewVal)
{
}
private:
int iVal = 5;
};
该类的不论什么构造器都将把iVal初始化为5,除非它提供自己的实现来改变这样的行
为。所以上面的空构造器将依照类的定义来初始化iVal,而接受int的构造器则
会用给定的參数来初始化iVal。
空指针
在现行标准中,常量0既是常量整数又是空指针,充当着双重角色。这一行为自
1972年C语言早期以来便一直存在。
多年来,程序猿为了避免这样的语义模糊多採用标识符NULL来取代0。然而,两项C++
语言的设计选择集中产生了还有一项语义模糊。在C语言中,NULL作为预编译宏被定
义为((void*)0)或0。在C++语言中,void*不能隐式转换为其它指针类型,因而在
前项定义下,简单如char* c = NULL的代码会通只是编译。为解决此问题,C++确
保NULL展开为0,并作为一种特例同意其转换为其它指针类型。这一选择在同重载
机制交互时产生了麻烦。比方,如果程序中存在下面声明:
void foo(char *);
void foo(int);
如今调用foo(NULL),则foo(int)版本号将会被调用,差点儿能够肯定这不是程序猿的
意图。
新标准非常可能引入一个专用于空指针的keyword,眼下nullptr承担这一角色。
nullptr不能被赋给整型,也不能与整型相比較,但它能够向其它指针类型赋值并
与之比較。
0的现存角色将会因显而易见的兼容性理由而得到保留。
假设新的语法取得成功,C++委员会可能会将把0和NULL当作空指针的做法标记为已
废弃特性,并终于废弃这样的双重角色。
强类型枚举
在标准C++语言中,枚举不是类型安全的。枚举值实际上就是整数,即便其枚举类
型各不同样。这样不同枚举类型的两个枚举值相互比較就成为可能。这方面C++03
所提供的唯一安全特性为:一个整数或一种枚举类型的值不能隐式转换为其它枚举
类型的值。另外,枚举所使用的整形的大小无法由用户指定,而仅仅能由实现定义。
最后,枚举值的作用域为枚举的外围作用域。这就意味着两个不同的枚举不能包括
名字同样的成员。
C++0x将同意创建一类没有上述问题的特殊枚举。这样的枚举通过enum class来声明:
enum class Enumeration
{
Val1,
Val2,
Val3 = 100,
Val4 //* = 101 *//,
};
这样的枚举是类型安全的。枚举类的值不会被隐式转换为整数,这样它们也不会被拿
来与整数相比較。(Enumeration::Val4 == 101会产生编译错误)
枚举类所使用的类型能够明白指定。缺省情况下,如同上例,为int。但这也能像
下例那样改变:
enum class Enum2 : unsigned int {Val1, Val2};
这样的枚举的作用域也被指定为枚举类名的作用域。使用这样的枚举名必须显式指定作
用域。Val1无定义,而Enum2::Val1有定义。
另外,C++0x也将同意标准枚举提供其作用域范围以及其使用的整型大小。
enum Enum3 : unsigned long {Val1 = 1, Val2};
这样的枚举名被定义具有枚举类型作用域,如(Enum3::Val1)。可是,为了保持向后
兼容,枚举名也被放入外围作用域。
枚举的前置声明在C++0x中也将成为可能。曾经,枚举类型不能前置声明的理由是
枚举的大小取决于其内容。仅仅要枚举值的大小在程序中得以指定,枚举就能前置声明。
enum Enum1; //Illegal in C++ and C++0x; no size is
explicitly specified.
enum Enum2 : unsigned int; //Legal in C++0x.
enum class Enum3; //Legal in C++0x, because enum class's
have a default type of "int".
enum class Enum4: unsigned int; //Legal C++0x.
enum Enum2 : unsigned short; //Illegal in C++0x, because Enum2 was
//previously declared with a different type.
尖括号
标准C++的词法分析器在不论什么场合下都将">>"解释为右移操作符。然而,在模板定
义中,如此解释两个右尖括号差点儿总是错误的。
C++0x将改变词法分析器的规范以便在合理的情况下能把多个右尖括号解释为模板
參数列表的结束符。能够用小括号来改变这样的行为。
template<bool bTest> SomeType;
std::vector<SomeType<1>2>> x1 ; // Interpreted as a std::vector of
SomeType<tr> 2>, which is not legal syntax. 1 is tr.
std::vector<SomeType<(1>2)>> x1 ; // Interpreted as std::vector of
SomeType<false>, which is legal C++0x syntax. (1>2) is false.
显式转换操作符
标准C++为构造器加入了explicitkeyword作为修饰符以防止仅仅有单个參数的构造器
使用隐式转换操作。然而,这样的做法对于真正的转换操作符是无效的。比方,一个
智能指针类可能有一个bool()操作符以使自身的行为更像原生指针。假设它包括这
种转换操作,它就能用if(smart_ptr_variable)来測试。(若指针非空则測试是为
真,否则測试为假。)然而,这样的操作也会导致其它出乎意料的转换。因为C++的
bool被定义为一种算术类型,这样的指针也就能够被隐式转换为整型甚而浮点类型,
从而导致一些用户并不希望出现的算术操作。
在C++0x中,explicitkeyword将能用于转换操作符的定义中。与构造器一样,它将
防止进一步的隐式转换。
模板typedef
在标准C++中,仅仅有在模板的全部參数全都指定的前提下才干对模板typedef。仅仅要
存在没有被指定的模板參数就不能使用typedef,比方:
template<typename first, typename second, int third> class SomeType;
template<typename second>
typedef SomeType<OtherType, second, 5> TypedefName; //Illegal in C++
这段代码不会通过编译。
C++0x将通过下面语法加入这样的能力:
template<typename first, typename second, int third> class SomeType;
template<typename second>
using TypedefName = SomeType<OtherType, second, 5>;
透明的垃圾收集
C++0x不会直接提供透明垃圾收集机制。作为替代,C++0x标准将包括一些有利于在
C++实现垃圾收集机制的特性。
对垃圾收集机制的全然支持将会延迟到标准的下一个版本号,或者是一个技术报告。
变參模板
标准C++的模板类和模板函数仅仅能接受一套固定的參数。C++0x将同意模板定义接受
随意数目的随意类型的參数。
template<typename... Vals> class tuple;
这个模板类/tuple/将可以接受随意数目的类型作为其參数。
class tuple<std::vector<int>,
std::map<std::string,
std::vector<int> > > someInstanceName;
參数的个数能够为0,所以/class tuple<>/ someInstanceName也能工作。
假设不想要接受0个參数的变參模板,使用下面定义就可以:
template<typename First, typename... Rest> class tuple;
变參模板将也能用于函数,如此将提供一种类似于标准C中函数变參机制的类型安
全的版本号。
template<typename... Params>
void printf(const std::string &strFormat, Params... parameters);
注意/.../操作符在模板參数列表中出如今类型/Params/的左边,而在函数签名中
则出如今类型的右边。当/.../操作符如同在模板參数列表中那样出如今类型的左
边时,它被称作打包运算符。该运算符规定类型可出现0次或多次。当/.../操作符
出如今类型的右边时,它被称作解包运算符。该运算符将导致该类型的复制,即复
制全部被打包运算符装入的类型。在上例中,函数printf将接受全部打包进入
Params的类型作为其參数。
变參模板一般是递归运用的。可变的參数本身并不直接向类或函数提供。由此,下
面展示了一种在C++0x中定义具有可变參数的/printf/的替代品的可行机制:
void printf(const char *s)
{
while (*s)
{
if (*s == '%' && *(++s) != '%')
throw std::runtime_error("invalid format string: missing arguments");
std::cout << *s++;
}
}
template < typename T, typename...Args >
void printf(constchar * s, T val, Args ... args)
{
while (*s)
{
if (*s == '%' && *(++s) != '%')
{
std::cout << val;
// call even when *s == 0 to detect extra arguments
printf(*s ? ++s : s, args...);
return;
}
std::cout << *s++;
}
throw std::logic_error("extra arguments provided to printf");
}
这是一个递归调用。注意变參模板版本号的/printf/调用其自身,在參数为空时则调
用简单版本号。
没有一种简单可行的机制可用于在模板变參上循环迭代。然而,使用解包运算符,
差点儿能够在不论什么地方解包并得到全部的模板參数。
比方,一个类使用例如以下定义:
template < typename...BaseClasses > class ClassName:p lic BaseClasses...
{
p lic:
ClassName(BaseClasses &&...baseClasses):
BaseClasses(std::move(baseClasses))...
{
}
}
代码中的解包运算符将复制全部类型并将其作为ClassName的基类。同一时候,构造器
必须接受全部基类的引用,以便初始化ClassName的全部基类。
关于函数模板,可变參数能够被前置。与右值引用相结合,便能实现完美转发:
template < typename TypeToConstr t >
str t SharedPtrAllocator
{
template< typename ... Args >
tr1::shared_ptr<TypeToConstr t>
Constr tWithSharedPtr(Args && ... params)
{
return tr1::shared_ptr <TypeToConstr t> (
new TypeToConstr t(std::move(params)...));
}
}
这段代码将參数列表解包并传入TypeToConstr t的构造器。使用
std::move(params)这种语法可以完美地将參数以对应的类型(甚至包含const属
性)传入构造器。解包运算符则将此转发语法传播至全部參数。代码中的工厂函数
还自己主动地将所分配的内存装进tr1::shared_ptr,这在某种程度上能够避免内存泄漏。
另外,模板參数包中实际參数的个数能够指定例如以下:
template < typename...Args > str t SomeStr t
{
static const int size = sizeof...(Args);
}
使用语法SomeStr t::size将得到2,而SomeStr t<>::size将返回0。
新的字符串常量
标准C++提供两种字符串常量。第一种,包括在双引號之间,生成一个以null结尾
的const char类型的数组。另外一种,形为L"",生成一个以null结尾的const
wchar_t类型的数组。这里wchar_t为宽字符。这两种字符串常量均没有对Unicode
编码的字符串提供支持。
为了改进C++编译器对Unicode的支持,char类型的定义被改动成除了包括编译器基
本运行字符集的全部成员之外还将至少包含UTF-8
中的8位编码。曾经它仅仅包括前者。
C++0x将包含三种Unicode编码的字符串分别用于支持UTF-8
, UTF-16
,及 UTF-32
。考虑到前面所讲述的对char定义的变
化,C++0x将会添加两种新的字符类型:char16_t和char32_t。这两种类型分别用
于保存UTF-16 和UTF-32。
下面代码展示怎样创建能用于这些编码的字符串常量:
"I'm a UTF-8 string."
u"This is a UTF-16 string."
U"This is a UTF-32 string."
第一种字符串的类型是常见的const char[]。另外一种字符串的类型是const
char16_t[]。第三种字符串的类型是const char32_t[]。
创建Unicode字符串常量时,通常须要在字符串中直接插入Unicode代码点。为
此,C++0x将同意例如以下语法:
"This is a Unicode Character: *\u*2018."
u"This is a bigger Unicode Character: *\u*2018."
U"This is a Unicode Character: *\u*2018."
'\u'之后的数是十六进制数,注意不须要使用常规的'0x'前缀。'\u'符号代表16位
Unicode代码点。假设须要32位Unicode代码点,可使用'\U'和一个32位十六进制
数。仅仅能使用有效的Unicode代码点。比方代码点区间U+D800—U+DFFF被禁止使用,
由于它们将被用作UTF-16编码对。
有时须要避免使用转移字符串,尤其是在使用XML
文件及脚本语言中。C++0x将提供原生字符
串常量:
R"[The String Data \ St? " ]"
R"delimiter[The String Data \ St? " ]delimiter"
第一种场合下,不论什么[ ]括号间的字符都是字符串的一部分。字符"和\无需使用转
义符。另外一种场合下,字符串開始于"delimiter[之后,并在碰到]delimiter"时结
束。字符串分隔符能够是随意字符串,这样用户就能在原生字符串中使用]字符。
原生字符串常量能够和宽字符串以及不论什么Unicode字符串结合使用。
用户自己定义的字面量
标准C++提供了几种字面量。字符串"12.5"被编译器解释为do le类型,值12.5。
然而,加了后缀"f"后,即"12.5f",就会产生包括值12.5的float类型。字面量的
后缀修饰符在C++规范中是固定的,C++代码无法创造新的字面量修饰符。
C++0x将会为用户提供创建新的字面量修饰符的能力。这样的新的字面量修饰符将根
据修饰字面量的字符串创建对象。
[](int x, int y) -> int { int z = x + y; return z + x; }
在此例中,暂时变量z被创建并用于存储中间值。和普通函数一样,中间值在多次函数调用之间不会被保存。
假设lambda函数不返回值,即返回值类型为void的话,该返回值类型也可全然省略。
在lambda函数作用域范围内被定义的变量的引用也能被使用。这些变量的合集通常被称为闭包。闭包能够定义并使用例如以下:
std::vector<int> someList;
int total = 0;
std::for_each(someList.begin(), someList.end(), [&total](int x) {
total += x
});
std::cout << total;
这段代码将显示列表中全部元素的总和。变量total将被存为该lambda函数对应的闭包的一部分。因为闭包变量total是栈变量total的引用,使用前者能够改变后者的值。