转自:http://blog.csdn.net/zwvista/article/details/2429781
原文请见http://en.wikipedia.org/wiki/C%2B%2B0x。
Rvalue reference and move semantics
右值引用与转移语义
在标准
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_value(
int
&&) {
return
true
; }
bool
is_r_value(
const
int
&) {
return
false
; }
void
test(
int
&& i)
{
is_r_value(i);
// false
is_r_value(std::
move
(i));
// true
}
出于右值引用定义的本质特征以及某些对左值引用(常规引用)定义的修改,现在右值引用允许程序员提供函数参数的完美转发。当与模板变参相结合时,这种能力可以允许函数模板完美地将参数转发给接受那些参数的其他函数。这在转发构造器参数时尤为有用:可以创建一些能自动调用具有相应参数构造器的工厂函数。
Generalized constant expressions
通用常量表达式
C++
语言一直具有常量表达式的概念。这些诸如
3+4
之类的表达式总是产生相同的结果且不具备副作用。常量表达式给编译器带来了优化的可能,而编译器也经常在编译期执行此类表达式并将结果存放在程序中。此外,
C++
语言规范中有一些地方需要使用常量表达式。定义数组需要常量表达式,而枚举值也必须是常量表达式。
然而,每当碰到函数调用或对象构造,常量表达式便不再有效。所以简单如下例便不合法:
int
GetFive() {
return
5
;}
int
some_value[GetFive() +
5
];
//create an array of 10 integers. illegal C++
这段代码在
C++
中不合法,因为
GetFive() + 5
不是一个常量表达式。编译器无从知晓
GetFive
在运行期是否产生常量。理论上,这个函数可能会影响某个全局变量,或者调用其他运行期产生非常量的函数。
C++0x
将引入
constexpr
关键字,此关键字将使用户能保证某个函数或构造器在编译期产生常量。上例可被改写如下:
constexpr
int
GetFive() {
return
5
;}
int
some_value[GetFive() +
5
];
//create an array of 10 integers. legal C++0x
这段代码将使编译器理解并确认
GetFive
是个编译期常量。
在函数上使用
constexpr
将对函数功能施加严格的限制。首先,函数必须返回非
void
类型。其次,函数体必须具有
"return expr"
的形式。第三,
expr
在参数替换后必须是常量表达式。该常量表达式只能调用其他定义为
constexpr
的函数,只能使用其他常量表达式数据变量。第四,常量表达式中一切形式的递归均被禁止。最后,这种带
constexpr
的函数在编译单元中必须先定义后调用。
变量也可被定义为常量表达式值。
constexpr
double
forceOfGravity =
9.8
;
constexpr
double
moonGravity = forceOfGravity /
6
;
常量表达式数据变量隐含为常量。它们只能存放常量表达式或常量表达式构造器的结果。
为了从用户自定义类型中构建常量表达式数据值,构造器在声明时可带
constexpr
。同常量表达式函数一样,在编译单元中常量表达式构造器也必须先定义后使用。常量表达式构造器函数体必须为空,而且它必须用常量表达式构造其成员。这种类型的析构器必须是平凡的。
由常量表达式拷贝构造的类型也必须被定义为
constexpr
,以使它们能从常量表达式函数中作为值被返回。类的任何成员函数,包括拷贝构造器和操作符重载,都能被声明为
constexpr
,只要它们符合常量表达式函数的定义。这就允许编译器在编译期不仅能拷贝类对象,也能对其实施其他操作。
常量表达式函数或构造器可以用非
constexpr
参数来调用。就如同一个
constexpr
整数常量可以被赋给一个非
constexpr
变量一样,
constexpr
函数也可用非
constexpr
参数来调用,并且其结果也可存放在非
constexpr
变量中。此关键字只是提供了在一个表达式的全部成员均为
constexpr
时其结果为编译期常量的可能性。
Modification to the definition of plain old data
对
POD
类型定义的修改
在标准
C++
语言中,要让结构成为
POD
类型必须满足某几条规则。有充分理由让一大堆类型满足这些规则(定义);只要满足这些规则,结构的实现将产生兼容于
C
的对象布局。然而,在
C++03
中这些规则过于严格。
C++0x
将放松某些关于
POD
的限制规则。
如果一个类或结构是平凡的,具有标准布局的,且不包含任何非
POD
的非静态成员,那么它就被认定是
POD
。平凡的
类或结构定义如下:
1.
具有一个平凡的缺省构造器。(可以使用缺省构造器语法,如
SomeConstructor() = default;
).
2.
具有一个平凡的拷贝构造器。(可以使用缺省构造器语法
).
3.
具有一个平凡的拷贝赋值运算符。(可以使用缺省语法
)
4.
具有一个非虚且平凡的析构器。
一个具有标准布局的类或结构被定义如下:
1.
所有非静态数据成员均为标准布局类型。
2.
所有非静态成员的访问权限
(public, private, protected)
均相同。
3.
没有虚函数。
4.
没有虚基类。
5.
所有基类均为标准布局类型。
6.
没有任何基类的类型与类中第一个非静态成员相同。
7.
要么全部基类都没有非静态数据成员,要么最下层的子类没有非静态数据成员且最多只有一个基类有非静态数据成员。总之继承树中最多只能有一个类有非静态数据成员。所有非静态数据成员必须都是标准布局类型。
Extern template
外部模板
在标准
C++
语言中,如果在某一个编译单元中编译器碰到一个参数完全指定的模板,它就必须具现化该模板。这种做法可能大大延长编译时间,尤其在许多编译单元使用同样的参数具现化该模板时。
C++0x
将引入外部模板的概念。
C++
已经拥有了迫使编译器在某一地点具现化模板的语法:
template
class
std::
vector
<MyClass>;
C++
所缺乏的是防止编译器具现化某个模板的能力。
C++0x
只是简单地将语法扩展为:
extern
template
class
std::
vector
<MyClass>;
这段代码将告诉编译器不要在这个编译单元具现化此模板。
Initializer lists
初始化列表
标准
C++
语言从
C
语言中借入了初始化列表概念。根据这一概念,结构或数组可以通过给定一串按照结构中成员定义的次序排列的参数来创建。初始化列表可以递归创建,因此结构数组或包含其他结构的结构也能使用初始化列表。这对于静态列表或用某些特定值初始化结构而言非常有用。
C++
语言中存在能让对象初始化的构造器特性。但构造器特性本身并不能取代初始化列表的所有功能。标准
C++
允许类和结构使用初始化列表,但它们必须满足
POD
的定义。非
POD
的类不能使用
初始化列表,一些
C++
式的容器如
std::vector
和
boost::array
也不行。
C++0x
将把初始化列表绑定为一种名为
std::initializer_list
的类型。这将允许构造器及其他函数接受初始化列表作为其参数。比如:
class
SequenceClass
{
public
:
SequenceClass(std::
initializer_list
<int> list);
};
这段代码将允许
SequenceClass
用一串整数构造,如下所示:
SequenceClass 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});
Uniform initialization
统一的初始化形式
标准
C++
在类型初始化中存在一些问题。语言中存在几种类型初始化方式,但替换使用的话产生的结果不尽相同。传统的构造语法看起来更像函数声明。必须采取措施以使编译器不把对象构造误认为函数声明。只有集合类型和
POD
类型能用集合初始化器初始化(用
SomeType var = {/*stuff*/};
)
.
C++0x
将提供一种能作用于任何对象的完全统一的类型初始化形式。这种形式对初始化列表语法作了扩展:
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
式的初始化列表。每个
public
变量都将用初始化列表中的值初始化。如果需要,隐式类型转化将被使用,并且如果没有隐式类型转化可供使用,编译器将报告编译失败。
var2
的初始化只是简单地调用构造器。
统一的初始化对象构造将消除在某些情况下指定类型的需要:
struct
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<SomeType>);
),
,那么只要初始化列表符合该构造器的类型,
初始化列表构造将优先于其他构造形式。
C++0x
版本的
std::vector
将拥有匹配与模板参数的
初始化列表构造器。这就意味着下面这段代码:
std::
vector
<int> theVec{
4
};
这段代码将调用初始化列表构造器,而不会调用
std::vector
中接受单个长度参数并创建相应长度的
vector
的构造器。为了调用后一个构造器,用户需要直接使用标准构造器语法。
Type determination
类型推定
在标准的
C++
和
C
语言中,变量在使用时必须明确指定其类型。然而,随着模板类型及模板元编程的到来,
表述某些定义完好的函数的返回值的类型变得不那么容易了。由此,在函数中存储中间值也变得困难了,用户有可能需要了解某个模板元编程库的内部结构才行。
C++0x
将通过两种方式来缓解这些困难。
首先,带有明确初始化的变量定义将可以使用
auto
关键字。这种初始化将创建与初始化器类型相同的变量。
auto
someStrangeCallableType = boost::
bind
(&SomeFunction, _2, _1, someObject);
auto
otherVariable =
5
;
someStrangeCallableType
的类型将等同于任何由
boost::bind
所返回的适合于这些特定参数的模板函数的类型。编译器很容易知道其类型,用户则不然。
otherVariable
的类型也定义完好,但用户更容易推定其类型。该变量是整形,也就是整型常量的类型。
另外,关键字
decltype
可用于在编译期确定某个表达式的类型。比如:
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
也
是一种减少代码的好方法。
Range-based for-loop
基于区间的
for
循环
C++
库
Boost
定义了几个区间概念。区间代表了与容器相类似的列表中两点之间的可控列表。已序容器是区间的超集。已序容器中的两个迭代器也能定义一个区间。这些概念和算法都将被融入
C++0x
的标准库中。然而,
C++0x
还将提供一种专用的语言设施来运用区间概念。
for
语句将使区间概念上的循环更容易:
int
my_array[
5
] = {
1
,
2
,
3
,
4
,
5
};
for
(
int
&x : my_array)
{
x *=
2
;
}
新的
for
循环的第一部分定义了用于在区间上循环的变量。和普通
for
循环中声明的变量一样,该变量的作用域也仅限于循环之内。置于
":"
之后的第二部分则表示将进行循环的区间。在这种情况下,存在一个约束映射可以将
C
式数组转化为区间。进行循环的区间还可以是
std::vector
,或任何符合
区间概念的对象。
Lambda functions and expressions
Lambda
函数及表达式
在标准
C++
语言中,尤其在使用诸如
sort
和
find
之类的标准库算法函数时,用户总是希望在算法函数调用的触发点附近定义谓词函数。在这一方面语言中只有一种机制可供利用:在函数中定义类。通常这种做法既啰嗦又笨重。另外,标准
C++
语言不允许在函数中定义的类充当模板参数,所以这种做法行不通。
显而易见,解决方案在于允许定义
lambda
表达式和
lambda
函数。
C++0x
将允许定义
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
被创建并用于存储中间值。和普通函数一样,中间值在多次函数调用之间不会被保存。
如果
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
的引用,使用前者可以改变后者的值。
为栈变量生成的闭包变量也可以不用引用操作符&
定义,这种情况下
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
value =
5
;
[&, value](
int
x) { total += (x * value) };
这段代码将导致
total
被存为引用,而
value
则会被存为拷贝。
如果一个
lambda
函数由一个类的某个成员函数定义,那么此
lambda
函数便被认定为该类的友元。这种
lambda
函数可以使用属于该类类型的对象的引用并访问其内部成员。
[](SomeType *typePtr) { typePtr->SomePrivateMemberFunction() };
只有当
lambda
函数在
SomeType
的某个成员函数中创建时这段代码才能工作。
对于指向当前成员函数所隶属对象的
this
指针,其处理有些特殊:必须在
lambda
函数中明确指定。
[
this
]() { this->SomePrivateMemberFunction() };
使用
[&]
或
[=]
形式将使
this
自动可用。
Lambda
函数是一些类型取决于编译器的函数对象。它们的类型只对编译器开放。如果用户希望把
lambda
函数当作参数,那么要么参数相应类型为模板,要么创建一个
std::function
用于保存
lambda
函数。使用
auto
关键字则可以将
lambda
函数保存在局部变量中。
auto
myLambdaFunc = [
this
]() { this->SomePrivateMemberFunction() };
然而,如果
lambda
函数的所有闭包变量均为引用,或者
lambda
函数根本没有闭包变量,那么所产生的函数对象将具有一种特殊类型:
std::reference_closure<R(P)>
。其中
R(P)
是带返回值的函数签名。这样做的理由在于期望此种类型的效率能好于使用
std::function
。
std::
reference_closure
<void()> myLambdaFunc = [
this
]() { this->SomePrivateMemberFunction() };
myLambdaFunc();
Unified function syntax
统一的函数语法
标准
C
语言的函数声明语法对于
C
语言的特性集来说是完美无缺的。由于
C++
语言
演化自
C
语言,
C++
语言保留了相关
的基本语法并在需要时进行扩充。然而,当
C++
变得更为复杂时,这种语法也暴露了一些局限性,尤其是在模板函数声明中。比如,以下代码在
C++03
中不合法:
template<typename LHS,
typename
RHS>
Ret
// NOT VALID!
AddingFunc(
const
LHS &lhs,
const
RHS &rhs) {
return
lhs + rhs;}
类型
Ret
为任何
LHS
和
RHS
相加所产生的类型。即使有了前面所讲述的
C++0x
的
decltype
功能,仍然不行:
template<typename LHS,
typename
RHS>
decltype(lhs+rhs)
// NOT VALID!
AddingFunc(
const
LHS &lhs,
const
RHS &rhs) {
return
lhs + rhs;}
这一段并非合法的
C++0x
代码,因为
lhs
和
rhs
尚未定义,只有在词法分析器分析出函数原型的其余部分之后这两者才能成为有效的标识符。
为解决这一问题,
C++0x
将引入一种新型的函数定义和声明的语法:
template<typename LHS,
typename
RHS>
[]
// C++0x lambda introducer
AddingFunc(
const
LHS &lhs,
const
RHS &rhs) -> decltype(lhs+rhs) {
return
lhs + rhs;}
这一语法也能用于更为普通的函数声明和定义中:
struct
SomeStruct
{
[]FuncName(
int
x,
int
y) ->
int
;
}
[]SomeStruct::
FuncName
(
int
x,
int
y) ->
int
{
return
x + y;
}
使用
[]
的语法与
lambda
函数完全相同。如果没有函数名,那么它就是一个
lambda
表达式。(由于
lambda
函数不能有模板,所以上面的例子不可能是
lambda
)既然函数可以在块区域中定义,用户也可以定义具名
lambda
函数:
[]Func1(
int
b)
{
[&]NamedLambda1(
int
a) ->
int
{
return
a + b;}
auto
NamedLambda2 = [&](
int
a) {
return
a + b;}
//lack of return type is legal for lambda specifications of this form.
}
这两句语句作用相同,即同样生成
std::reference_closure<int(int)>
类型的变量。但是,下例则有所不同:
[]Func1()
{
[] NamedLambda1 (
int
a) ->
int
{
return
a +
5
;}
// nested local function, valid in C++0X
auto
NamedLambda2 = [](
int
a) {
return
a +
5
;}
}
变量
NamedLambda1
是一个常规
C
式函数指针,就如同它被定义成
int NamedLambda1(int a)
那样。变量
NamedLambda2
则被定义成
std::reference_closure<int(int)>
类型。前一行展示了
C++0x
为嵌套函数所准备的新形式。
Concepts
约束
在
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;
}
关键字requires之后为一串约束的声明。它可以被用于表述涉及多个类型的约束。此外,如果用户希望当类型匹配该约束时不要使用某个特定模板,也可以用requires !LessThanComparable<T>
。可以像模板特化那样使用这种机制。一个通用模板可能通过显式禁用一些特性丰富的约束来处理具有较少特性的类型。而这些约束则可通过特化利用某些特性来取得更高的效率并实现更多的功能。
约束定义如下:
auto
concept LessThanComparable<typename T>
{
bool
operator<(T, T);
}
这个例子中的关键字auto意味着任何类型只要支持约束中所指定的操作便被认定支持该约束。如果不使用auto
关键字,为声明某个类型支持该约束就必须对该类型使用约束映射。
该约束声明任何类型只要定义了接受两个参数
并返回
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
Value>
{
requires Regular<Iter>;
Value operator*(
const
Iter&);
Iter& operator++(Iter&);
Iter operator++(Iter&,
int
);
}
InputIterator约束的第一个模板参数必须符合Regular
约束。
与继承相似,约束也可派生自另一约束。与类继承相似,满足派生约束所有限制条件的类型必须满足基本约束的所有限制条件。约束派生定义形同类派生:
concept ForwardIterator<typename Iter,
typename
Value> : InputIterator<Iter, Value>
{
//Add other requirements here.
}
类型名可以与约束相关。这将施加一些限制条件:在使用这些约束的模板中,这些类型名可供使用:
concept InputIterator<typename Iter>
{
typename
value_type;
typename
reference;
typename
pointer;
typename
difference_type;
requires Regular<Iter>;
requires Convertible<reference, value_type>;
reference operator*(
const
Iter&);
// dereference
Iter& operator++(Iter&);
// pre-increment
Iter operator++(Iter&,
int
);
// post-increment
// ...
}
约束映射允许某些类型被显式绑定到某个约束。如有可能,约束映射也允许在不改变类型定义的前提下让该类型采用某个约束的语法。比如下例:
concept_map InputIterator<char*>
{
typedef
char
value_type ;
typedef
char& reference ;
typedef
char
* pointer ;
typedef
std::
ptrdiff_t
difference_type ;
};
这个约束映射填补了当InputIterator映射作用于char*
类型时所需要的类型名。
为增加灵活性,约束映射本身也能被模板化。上例可以被延伸至所有指针类型:
template<typename T> concept_map InputIterator<T*>
{
typedef
T value_type ;
typedef
T& reference ;
typedef
T* pointer ;
typedef
std::
ptrdiff_t
difference_type ;
};
此外,约束映射也可充当迷你类型,此时它会包含函数定义以及其他与类相关的结构设施:
concept Stack<typename X>
{
typename
value_type;
void
push(X&,
const
value_type&);
void
pop(X&);
value_type top(
const
X&);
bool
empty(
const
X&);
};
template<typename T> concept_map Stack<std::
vector
<T> >
{
typedef
T value_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
的调用。最终,这种做法将允许一个已经存在的对象在不改变其定义的前提下,转换其接口并为模板函数所利用。
最后需要指出的是,某些限制条件可以通过静态断言来检测。这种手段可以用来检测那些模板需要但却面向其他方面问题的限制条件。
Object construction improvement
对象构建方面的改进
在标准
C++
语言中,构造器不能调用其他构造器。每个构造器要么独自构建类的所有成员要么调用某个公共成员函数。基类的构造器不能直接暴露给派生类。类的非静态数据成员不能在其声明的场所初始化,它们只能在构造器中初始化。
C++0x
将为所有这些问题提供解决方案。
C++0x
将允许构造器调用其他伙伴构造器(被称为委托)。如此,只需添加少量代码,构造器便能利用其他构造器的行为。另外一些语言,
比如
Java
和
C#
,允许这样做。
语法如下:
class
SomeType
{
int
number;
public
:
SomeType(
int
newNumber) : number(newNumber) {}
SomeType() : SomeType(
42
) {}
};
这就产生了一个问题:
C++03
认为一个对象在其构造器执行完毕时才能构建完成,而
C++0x
则认为一个对象在任何构造器执行完毕时都将构建完成。由于多个构造器被允许执行,这将导致每个委托构造器都可能在一个已经构造完成的对象上执行操作。派生类构造器将在基类的所有委托构造器执行完毕后执行。
关于基类构造器,
C++0x
将允许一个类指定需要继承的
基类构造器。这意味着
C++0x
编译器将产生代码用于类继承,即将子类构造转发为基类构造。注意这是一个要么全部要么没有的特性:或者全部构造器被转发,或者没有构造器被转发。另外注意在多重继承的情况下有些限制,比如类构造器不能继承来自两个类的具有相同签名的构造器。同时子类构造器的签名也不能与用来继承的基类构造器相匹配。
相应语法如下:
class
BaseClass
{
public
:
BaseClass(
int
iValue);
};
class
DerivedClass :
public
BaseClass
{
public
:
using
default
BaseClass;
};
关于成员初始化,
C++0x
将允许以下语法:
class
SomeClass
{
public
:
SomeClass() {}
explicit
SomeClass(
int
iNewValue) : iValue(iNewValue) {}
private
:
int
iValue =
5
;
};
该类的任何构造器都将把
iValue
初始化为
5
,除非它提供自己的实现来改变这种行为。所以上面的空构造器将按照类的定义来初始化
iValue
,
而接受
int
的构造器则会用给定的参数来初始化
iValue
。
Null pointer
空指针
在现行标准中,常量
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)
版本将会被调用,几乎可以肯定这不是程序员的意图。
新标准很可能引入一个专用于空指针的关键字,目前
nullptr
承担这一角色。
nullptr
不能被赋给整型,也不能与整型相比较,但它可以向其他指针类型赋值并与之比较。
0
的现存角色将会因显而易见的兼容性理由而得到保留。
如果新的语法取得成功,
C++
委员会可能会将把
0
和
NULL
当作空指针的做法标记为已废弃特性,并最终废弃这种双重角色。
Strongly typed enumerations
强类型枚举
在标准
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.
Angle bracket
尖括号
标准
C++
的词法分析器在任何场合下都将
">>"
解释为右移操作符。然而,在模板定义中,如此解释两个右尖括号几乎总是错误的。
C++0x
将改变词法分析器的规范以便在合理的情况下能把多个右尖括号解释为模板参数列表的结束符。可以用小括号来改变这种行为。
template<bool bTest> SomeType;
std::
vector
<SomeType<
1
>
2
>> x1 ;
// Interpreted as a std::vector of SomeType<true> 2>, which is not legal syntax. 1 is true.
std::
vector
<SomeType<(
1
>
2
)>> x1 ;
// Interpreted as std::vector of SomeType<false>, which is legal C++0x syntax. (1>2) is false.
Explicit conversion operators
显式转换操作符
标准
C++
为构造器添加了
explicit
关键字作为修饰符以防止只有单个参数的构造器使用隐式转换操作。然而,这种做法对于真正的转换操作符是无效的。比如,一个智能指针类可能有一个
bool()
操作符以使自身的行为更像原生指针。如果它包含这种转换操作,它就能用
if(smart_ptr_variable)
来测试。(若指针非空则测试是为真,否则测试为假。)然而,这种操作也会导致其他出乎意料的转换。由于
C++
的
bool
被定义为一种算术类型,这种指针也就可以被隐式转换为整型甚而浮点类型,从而导致一些用户并不希望出现的算术操作。
在
C++0x
中,
explicit
关键字将能用于转换操作符的定义中。与构造器一样,它将防止进一步的隐式转换。
Template typedefs
模板
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
>;
Transparent garbage collection
透明的垃圾收集
C++0x
不会直接提供透明垃圾收集机制。作为替代,
C++0x
标准将包含一些有利于在
C++
实现垃圾收集机制的特性。
对垃圾收集机制的完全支持将会延迟到标准的下一个版本,或者是一个技术报告。
Variadic templates
变参模板
标准
C++
的模板类和模板函数只能接受一套固定的参数。
C++0x
将允许模板定义接受任意数目的任意类型的参数。
template<typename...
Values
>
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;
Variadic templates may also apply to functions, thus providing a type-safe mechanism similar to the standard C variadic function mechanism:
变参模板将也能用于函数,如此将提供一种类似于标准
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
(
const
char
* s, T value, Args...
args
)
{
while
(*s)
{
if
(*s ==
'%'
&& *(++s) !=
'%'
)
{
std::
cout
<<
value
;
printf
(*s ? ++s : s, args...);
// call even when *s == 0 to detect extra arguments
return
;
}
std::
cout
<< *s++;
}
throw std::
logic_
error
(
"extra arguments provided to printf"
);
}
这是一个递归调用。注意变参模板版本的printf
调用其自身,在参数为空时则调用简单版本。
没有一种简单可行的机制可用于在模板变参上循环迭代。然而,使用解包运算符,几乎可以在任何地方解包并得到所有的模板参数。
比如,一个类使用如下定义:
template
<typename...
BaseClasses
>
class
ClassName :
public
BaseClasses...
{
public
:
ClassName (BaseClasses&&...
baseClasses
) : BaseClasses(std::
move
(baseClasses))... {}
}
代码中的解包运算符将复制所有类型并将其作为
ClassName
的基类。同时,构造器必须接受所有基类的引用,以便初始化
ClassName
的所有基类。
关于函数模板,可变参数可以被前置。与右值引用相结合,便能实现完美转发:
template<typename TypeToConstruct>
struct
SharedPtrAllocator
{
template<typename ...
Args
> tr1::
shared_ptr
<TypeToConstruct> ConstructWithSharedPtr(Args&&...
params
)
{
return
tr1::
shared_ptr
<TypeToConstruct>(
new
TypeToConstruct(std::
move
(params)...));
}
}
这段代码将参数列表解包并传入
TypeToConstruct
的构造器。使用
std::move(params)
这样的语法能够完美地将参数以相应的类型(甚至包括
const
属性
)传入
构造器。解包运算符则将此转发语法传播至所有参数。代码中的工厂函数还自动地将所分配的内存装进
tr1::shared_ptr
,这在某种程度上可以避免内存泄漏。
另外,模板参数包中实际参数的个数可以指定如下:
template<typename ...
Args
>
struct
SomeStruct
{
static
const
int
size =
sizeof
...(
Args
);
}
使用语法
SomeStruct<Type1, Type2>::size
将得到
2
,而
SomeStruct
<>::size
将返回
0
。
New string literals
新的字符串常量
标准
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
。
以下代码展示如何创建能用于这些编码的字符串常量:
u8
"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
将允许如下语法:
u8
"This is a
Unicode
Character: /u2018."
u
"This is a
bigger
Unicode Character: /u2018."
U
"This is a
Unicode
Character: /u2018."
'/u'
之后的数是十六进制数,注意不需要使用常规的
'0x'
前缀。
'/u'
符号代表
16
位
Unicode
代码点。如果需要
32
位
Unicode
代码点,可使用
'/U'
和一个
32
位十六进制数。只能使用有效的
Unicode
代码点。比如代码点区间
U+D800—U+DFFF
被禁止使用,因为它们将被用作
UTF-16
编码对。
有时需要避免使用转移字符串,尤其是在使用
XML
文件及脚本语言中。
C++0x
将提供原生字符串常量:
R"[The String Data
/
Stuff " ]"
R"delimiter[The String Data / Stuff " ]delimiter"
第一种场合下,任何
[
]
括号间的字符都是字符串的一部分。字符
"
和
/
无需使用转义符。第二种场合下,字符串开始于
"delimiter[
之后,并在碰到
]delimiter"
时结束。字符串分隔符可以是任意字符串,这样用户就能在原生字符串中使用
]
字符。
原生字符串常量可以和宽字符串以及任何
Unicode
字符串结合使用。
User-defined literals
用户自定义的字面量
标准
C++
提供了几种字面量。字符串
"12.5"
被编译器解释为
double
类型,值
12.5
。然而,加了后缀
"f"
后,即
"12.5f"
,就会产生包含值
12.5
的
float
类型。字面量的后缀修饰符在
C++
规范中是固定的,
C++
代码无法创造新的
字面量修饰符。
C++0x
将会为用户提供创建新的
字面量修饰符的能力。这种
新的
字面量修饰符将根据修饰字面量的字符串创建对象。
字面量转换被重定义为两个阶段;未加工(
raw
)形式和已加工(
cooked
)形式。一个未加工的字面量是具有某种类型的一串字符,而一个已加工的字面量则具有其他某种类型。
C++
字面量
1234
,作为
未加工字面量,是一串字符
'1', '2', '3', '4'
。而作为已加工字面量,则是整数
1234
。
C++
字面量
0xA
在
未加工形式下是
'0', 'x', 'A'
,而在已加工形式下则是整数
10
。
字面量可以被同时处理为未加工和已加工形式,但是字符串常量除外,后者只能被处理为已加工形式。这样处理的原因在于字符串的前缀会影响所修饰字符的含义及类型。
所有用户自定义的字面量都只能定义后缀而不能定义前缀。
下面的代码定义了处理未加工形式的用户自定义字面量:
OutputType operator
"
Suffix
"
(
const
char
*literal_string);
OutputType someVariable = 1234Suffix;
第二句语句执行由用户自定义字面量函数所定义的代码。该函数将被传入一个
C
式的由
null
结尾的字符串
"1234"
。
作为替代方式,处理未加工形式的字面量还可通过模板变参来进行:
template<char...> OutputType operator
"Suffix"
();
OutputType someVariable = 1234Suffix;
这段代码将使字面量处理函数被具现化为
operator"
Suffix
"<'1', '2', '3', '4'>
。在这种形式下,字符串中没有用于结尾的
null
字符。这样做的目的在于允许字面量在编译期被完全转换(假设
OutputType
可以被创建为常量表达式并且字面量处理函数也是一个常量表达式函数
)。
对于已加工形式的字面量,其类型将被直接使用,并且没有可供替代的模板形式:
OutputType operator
"
Suffix
"
(
int
the_value);
OutputType someVariable = 1234Suffix;
对于字符串常量,根据如前所述的新的字符串修饰符来决定所调用的版本:
OutputType operator
"Suffix"
(
const
char
* string_values,
size_t
num_chars);
OutputType operator
"
Suffix
"
(
const
wchar_t
* string_values,
size_t
num_chars);
OutputType operator
"
Suffix
"
(
const
char16_t * string_values,
size_t
num_chars);
OutputType operator
"
Suffix
"
(
const
char32_t * string_values,
size_t
num_chars);
OutputType someVariable =
"1234"
Suffix;
//Calls the const char * version
OutputType someVariable = u8
"1234"
Suffix;
//Calls the const char * version
OutputType someVariable = L
"1234"
Suffix;
//Calls the const wchar_t * version
OutputType someVariable = u
"1234"
Suffix;
//Calls the const char16_t * version
OutputType someVariable = U
"1234"
Suffix;
//Calls the const char32_t * version
字符常量可类似得到定义。
Multitasking memory model
多任务内存模型
C++
标准委员会计划为多线程提供标准化支持。
有两部分将被涉及:对允许多线程在同一个程序中共存的内存模型提供定义,对这些线程间的交互提供支持。后者将通过类设施来实现。
内存模型对于描述多个线程在何种情况下可以访问相同的内存地址是不可或缺的。一个遵守相应规则的程序可以保证运行正常,而一个破坏相应规则的程序则可能会因为编译器的优化行为以及内存一致性等问题而出现不可预料的行为。
Thread-local storage
线程局部存储
在多线程环境下,每个线程通常都拥有一些自己所独有的变量。对于函数的局部变量来说确实如此,而对于全局以及静态变量来说就不是这样。
除了现存的静态,动态以及自动存储方式以外,在下一个标准中还将提议增加一种局部于线程的存储方式。线程局部存储方式将通过
thread_local
存储指示符来指定。
一个本来可能具有静态存储方式的对象(即生命期跨越程序整个执行过程的对象)现在可能会被赋予线程存储方式。与静态存储的对象相似,线程局部的对象应该也能用构造器构建并用析构器销毁。
Defaulting/deleting of standard functions on C++ objects
显式使用
/
不使用
C++
类的某些(缺省)成员函数
在标准
C++
语言中,如果对象自己不提供,编译器会自动产生一个缺省构造器,一个拷贝构造器,一个拷贝赋值运算符
operator=
,以及一个析构器。如前所述,用户可以通过提供自己的版本来覆盖这些缺省实现。
C++
还定义了一些能作用于所有类的全局操作符(如
operator=
和
operator new
),当然用户也能覆盖其实现。
问题在于用户对于这些默认产生的函数缺乏控制。例如,如果需要使某个类不可拷贝,用户需要声明一个私有的拷贝构造器,一个拷贝赋值运算符,同时略去这些函数的定义。试图使用这些函数将产生编译错误或链接错误。然而这不是一个理想的解决方案。
而且,对于缺省构造器而言,明确告诉编译器产生这类函数通常非常有用。如果对象已经具有了任何构造器,编译器就不会为它提供缺省构造器。这在许多场合下的确有用,但有时也需要同时拥有一个编译器产生的缺省构造器以及另一个专用构造器。
C++0x
将允许用户明确指定使用或不使用这些标准的对象函数。比如,下面这个类型明确声明它会使用缺省构造器。
struct
SomeType
{
SomeType() = default;
//The default constructor is explicitly stated.
SomeType(OtherType value);
};
另外,一些特性也能得到明确禁止。比如,下面这个类型是不可拷贝的。
struct
NonCopyable
{
NonCopyable & operator=(
const
NonCopyable&) =
delete
;
NonCopyable(
const
NonCopyable&) =
delete
;
NonCopyable() = default;
};
一个类型可以明确禁止用
operator new
分配:
struct
NonNewable
{
void
*operator
new
(std::
size_t
) =
delete
;
};
这个对象只能在栈里面分配或成为其他类型的成员。不使用不可移植的手段将无法在堆中分配该对象。由于使用就地分配
new
操作符是调用构造器在用户所分配的内存中创建对象的唯一途径,并且这一途径已经被上述代码所禁止,可以肯定对象将无法被适当创建。
指示符
= delete
可被用于禁止调用任意函数,当然它也能用于禁止调用具有某些特定参数的成员函数。比如:
struct
NoDouble
{
void
f(
int
i);
void
f(
double
) = delete;
};
编译器将会拒绝
用
double
值调用
f()
的企图,而不会无声无息地将其转换为
int
。这种方法可以被一般化从而禁止使用
int
以外的任何类型调用该函数,代码如下:
struct
OnlyInt
{
void
f(
int
i);
template<class T>
void
f(T) =
delete
;
};
Type long long int
long long int
类型
在
32
位系统中,存在一种至少
64
位的
long long
整数类型十分有用。
C99
标准将此类型引入标准
C
,大多数
C++
编译器也早就将它作为一种扩展。事实上,一些编译器在
C99
引入它很久之前就已经提供支持。
C++0x
将把这种类型加入标准
C++
。
Static assertions
静态断言
C++
标准提供了两种测试断言的方法:宏
assert
和预编译指示符
#error
。然而这两者在模板中使用均不适合:
宏
assert
在运行期测试断言,而预编译指示符
#error
则在模板具现化之前的预编译期测试断言。两者均不适合于测试依赖于模板参数的属性。
新标准将会使用新的关键字
static_assert
来为编译器检查断言提供一种新的方式。声明形式如下:
static_assert( constant-expression, error-message ) ;
以下是一些使用
static_assert
的例子:
static_assert(
3.14
< GREEKPI && GREEKPI <
3.15
,
"GREEKPI is inaccurate!"
) ;
template<
class
T >
struct
Check
{
static_assert(
sizeof
(
int
) <=
sizeof
(T),
"T is not big
enough
!"
) ;
} ;
当常量表达式的值为
false
时,编译器会产生错误信息。第一个例子给出了替代预编译指令
#error
的一种方案。与此相对应,在第二个例子中,每次模板类
Check
具现化时都将检查断言。
静态断言在模板之外也很有用。比如,一个算法的某个实现可能取决于某个
C++
标准无法保证的断言,如
long
的长度大于
int
。
(
在这种情况下
将
long
替换为
long long
可能更为可行。这样的假设在大多数(不是全部)系统中都将有效
)
Allow 'sizeof' to work on members of classes without an explicit object
允许
'sizeof'
在没有提供类实例的前提下作用于类的成员
在标准
C++
语言中,
sizeof
操作符只能用于类型和对象。而它不能用于以下场合:
struct
SomeType { OtherType member; };
sizeof
(SomeType::member);
//Does not work.
这段代码将返回
OtherType
的大小。
C++03
不允许这么做,所以它会产生编译错误。
C++0x
将允许这么做。