您的位置:首页 > 编程语言 > C语言/C++

C++0x 走马观花:核心语言可用性的增强

2008-10-27 20:45 295 查看
5 核心语言可用性的增强
这些特征存在的主要目的是便于语言更加容易地被使用。它们可以提高类型安全,最小化代码冗余,防止编程失误的出现,以及类似的情况。

5.1 初始化列表
标准C++从C语言里面借用了初始化列表这个概念。也就是说一个结构或者数组可以依照成员变量定义顺序的参数列表进行创建。初始化列表还可以是嵌套的,所以结构体数组或者包含其他结构体的结构也可以使用初始化列表。这在静态链表或者仅仅将结构初始化为特定值的时候是很有用的。C++拥有构建函数,它也可以对对象进行初始化,但单独构建函数还是无法替代传统初始化列表的所有功能。标准C++允许结构和类进行这种初始化,不过要求对象必须满足POD(Plain Old Data)的定义;而非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};

这种构建函数是一类特殊的构建函数,被称为初始化列表的构建函数。拥有这种构建函数的类在统一初始化(uniform initialization)的时候需要被特殊对待。

类std::initializer_list<>是第一级别的C++标准库类型。然而,它们只能通过使用{ }语法,静态地被C++0x编译器在初始化时被构建出来。一旦构建,这个列表就可以被拷贝,尽管这只是引用拷贝。初始化列表是恒定的,一旦被创建出来它的成员就无法被改变,而且它的成员所包含的数据也无法被改变。

由于初始化列表是一个实际的类型,它也可以在类构建函数之外的地方使用。常规函数可以使用有类型的初始化列表作为参数,比如:

void FunctionName(std::initializer_list<float> list);

FunctionName({1.0f, -3.45f, -0.4f});

标准的容器也可以按照这种方式进行初始化:

vector<string> v = { "xyzzy", "plugh", "abracadabra" };

5.2 统一初始化(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的那个构建函数。为了使用构建函数,使用者需要直接去使用标准构建函数语法。

5.3 类型检测
在标准C++(以及C)里面,为了使用一个变量,它的类型必须显式指明。然而,随着模板类型和模板元编程技术的出现,有些东西的类型,特别是定义好的函数返回的类型,就没那么容易去表示出来。所以,在变量里存放中间结果就困难了起来,因为这可能还需要特定元编程库内部的知识。

C++0x采用两种方式进行演进。第一种,显式初始化变量的定义可以使用auto关键字,这可以创建初始化特定类型的变量:

auto someStrangeCallableType = boost::bind(&SomeFunction, _2, _1, someObject);
auto otherVariable = 5;

不管重载boost::bind的特定模板函数返回什么,someStrangeCallableType的类型简单地使用它们就可以了。审视并检测这件事清,对编译器这很容易,而对用户却不容易。

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也可以用来减少冗余的代码量,但当程序员开始使用嵌套容器的时候,区别就会越来越大。

5.4 基于范围的for循环
boost C++库定义了一系列"范围(range)"的概念。范围表示在列表两端点之间的可控列表,好像容器一样。有序容器是范围这个概念的超集,在有序容器里面的两个迭代器也可以被定义为范围。这些概念和操作它们的算法,要被集成到C++0x标准库里面。而且,C++0x要提供相关语言特征来表示范围概念的功能。

语句for很容易的使得在范围上进行轮询:

int my_array[5] = {1, 2, 3, 4, 5};
for(int &x : my_array)
{
x *= 2;
}

新的for循环的第一部分定义了要在范围内进行迭代的变量。这个作为声明在循环体内部的变量,仅仅在循环内部有效。第二部分,也就是在":"之后的部分,表示要被轮询的范围。这里就有个概念对应,允许将C风格的数组转换为范围概念。它也可以是std::vector,或者任何遵循范围概念的对象。

5.5 lambda函数和表达式
在标准C++里面,特别和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的引用,所以它的值可以改变。

对于栈变量的闭包变量也可以被定义为不要引用符号&,这表示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函数可以在原来变量的生命期结束之后还可以继续使用它们。

列表里面还可以是使用缺省的情况。比如,如果用户希望通过引用获得大部分变量,而不是通过值获得一个,那么用户可以按照下面这样做:

int total = 0;
int value = 5;
[&, value](int x) { total += (x * value) };

这里total被作为引用存放,而value作为拷贝来存放。

如果lambda函数被类的成员函数所定义,它就被认为是这个类的友元。这样的lambda函数可以使用类相应对象的引用,也可以使用它的内部成员:

[](SomeType *typePtr) { typePtr->SomePrivateMemberFunction() };

如果lambda函数的创建范围在SomeType的成员函数里面时,上面的例子才成立。

在处理指向当前成员函数相应对象的this指针的时候,情况就比较特殊。它必须显式地在lambda函数里面设定:

[this]() { this->SomePrivateMemberFunction() };

使用[&]或者[=]形式的lambda函数将自动做到这一点。

lambda函数是依赖于编译器类型的函数对象,对象的类型名字仅仅对编译器可见。如果用户希望使用lambda函数作为参数,那么类型必须是一个模板类型,或者必须创建一个std::function去取得lambda的值。使用auto关键字可以在本地存放lambda函数:

auto myLambdaFunc = [this]() { this->SomePrivateMemberFunction() };

然而,如果lambda函数获得了被引用的所有闭包变量,或者如果它没有闭包变量,那么产生的函数对象使用下面特殊的类型:std::reference_closure<R(P)>,其中R(P)是有返回值的函数签名。对于lambda函数,这应该是一种与std::function获得相比更有效的表达方式:

std::reference_closure<void()> myLambdaFunc = [this]() { this->SomePrivateMemberFunction() };
myLambdaFunc();

5.6 统一的函数语法
标准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>
[]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函数不能是模板,所以AddingFunc这个例子也就不能是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)一样,而变量variable NamedLambda2则是被定义为类型std::reference_closure<int(int)>。第一行展示了C++0x里嵌套函数的新的形式。

5.7 概念设计
C++里面,模板类和函数对它们用到的类型都有必要的约束。比如,STL容器要求被存放的类型必须是可以赋值的。和由类的多重继承呈现出来的动态多态不一样,在那里,一个接受对象类型Foo&的函数就可以被传入任何Foo的子类型,而这里,只要一个类支持用到模板的所有的操作,这个类就可以作为模板参数提供。在普通函数的情况,函数参数必须满足什么要求一目了然(作为Foo的子类型),但是对于模板的情况,对象接口是什么样子的却是隐晦的,它隐藏于模板的实现之中。概念(Concepts)提供了一种方式对模板参数需要满足的接口进行代码化。

引入concepts的主要动机是提高编译器报错信息的质量。如果一个程序员试图去使用某种类型,这种类型不满足模板要求的接口,这时编译器将会报错。然而这种错误常常难以理解,尤其是对于初学者而言。有两种主要的原因。第一,错误的消息常常和模板的参数一起全部展示出来,这就导致大量的错误消息。在一些编译器上,一个简单的错误就会产生数以K计的错误消息。此外,它们通常不指向实际发生错误的位置。例如,当一个程序员试图去构建一个没有拷贝构建函数的对象的向量(vector)时,第一个错误往往指向vector类内部的代码,那里正在试图进行拷贝构建内容;这个程序员必须足够有经验才能认识到,真正的错误其实是类型不支持vector类所要求的。

为了解决这个问题,C++0x添加了concepts这个语言特征。类似于OOP如何使用基类去定义关于类型操作的限制,concept也是一种有名字的构建,它指明需要提供什么样的类型。和OOP不一样的地方是,concept自己的定义常常与要传入模板的类型没有明显的关系,而仅仅是模板定义本身:

template<LessThanComparable T>
const T& min(const T &x, const T &y)
{
return y < x ? y : x;
}

它不对模板类型参数使用强制的类或者类型的名字,而使用被预先定义好的concept: LessThanComparable。如果传入min模板函数的类型不满足LessThanComparable要求的话,编译错误就会产生,告诉使用者用来实例化模板的类型不符合LessThanComparable这个concept。

关于concept更为通用的形式如下:

template<typename T> requires LessThanComparable<T>
const T& min(const T &x, const T &y)
{
return y < x ? y : x;
}

关键字requires标识concept声明序列的开始。它也可以被用于使用多种类型的concept。此外,假如用户希望不要使用匹配这个concept的特定模板,可以写作requires !LessThanComparable<T>。类似地,这种技术可以用于模板的例化。对通用模板显式的禁用某些或者更加feature-rich的concepts,可以让它仅仅处理少量的一些feature。从而这些concept可以有它们自己的例化,使用这些特定的feature可以获得更好的性能或者其他的功能。

一个concept可以定义如下:

auto concept LessThanComparable<typename T>
{
bool operator<(T, T);
}

在这个例子里面的关键字auto表示,凡是支持concept里面设定操作的所有类型都可以认为是符合concept的。如果没有auto这个关键字,类型就必须使用concept map去声明它自己是支持这种concept的。

这个concept声明任何类型,只要它支持operator <,使用两个同样类型的对象作为参数,而且返回一个bool类型,就被认为是LessThanComparable。运算符不必是free-function;它还可以是类型T的成员函数。

concept也可以涉及到多个对象,比如,concept可以表示某种类型是可以转换的:

auto concept Convertible<typename T, typename U>
{
operator U(const T&);
}

为了在模板里使用它,就必须使用通用形式的concept用法:

template<typename U, typename T> requires Convertible<T, U>
U convert(const T& t)
{
return t;
}

Concept可以进行组合,比如,给定一个concept名字为Regular:

concept InputIterator<typename Iter, typename Value>
{
requires Regular<Iter>;
Value operator*(const Iter&);
Iter& operator++(Iter&);
Iter operator++(Iter&, int);
}

InputIterator这个concept的第一个模板参数必须遵循Regular这个concept。

类似于继承,concept还可以从另外一个concept派生出来。同样地,类型不仅仅要满足派生concept的要求,还要满足base concept的要求。它按照类派生的语法定义:

concept ForwardIterator<typename Iter, typename Value> : InputIterator<Iter, Value>
{
//Add other requirements here.
}

类型名字也可以和某个concept关联。这要求在使用concept的模板里面,类型名字是可用的。

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允许类型显式地绑定到一个concept上面。只要可能,也允许类型采用concept的语法,而不需要改变类型的定义,示例:

concept_map InputIterator<char*>
{
typedef char value_type ;
typedef char& reference ;
typedef char* pointer ;
typedef std::ptrdiff_t difference_type ;
};

如果适用于char*类型的时候,这个map就会填充InputIterator所要求的类型名字。

作为增加的便利程度,concept map本身也可以模板化。上面的例子可以扩展到所有的指针类型:

template<typename T> concept_map InputIterator<T*>
{
typedef T value_type ;
typedef T& reference ;
typedef T* pointer ;
typedef std::ptrdiff_t difference_type ;
};

而且,通过普通的和类相关的函数定义以及其他构建,concept map可以起到最小类型的作用:

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(); }
};

这个concept map允许使用模板使用std::vector,模板使用的类型来自于实现Stack的类型,使得将函数调用直接重映射到了std::vector调用。最终,这将已存在对象转换成模板函数可以使用的接口,而不需要接触到对象的定义。

最后,需要注意的是可以使用静态断言来检查某些requirement。这可以验证模板的某些requirement,但实际上是另外的问题。

5.8 构建对象的改进
标准C++里面,构建函数不允许调用其他的构建函数;每个构建函数必须构建自己所有的类成员,或者调用通用的成员函数。基类的构建函数不能直接的展示给派生类;每个派生类必须实现构建函数,哪怕是基类构建函数已经很近似了。类的非constant数据成员不能在成员声明的地方进行初始化。它们只能在构建函数的地方进行初始化。

C++0x将为所有这些问题提供解决方案。

C++0x允许构建函数调用其他构建函数(被称为delegation)。这使得构建函数可以使用非常少的代码来利用其他构建函数的行为。其他语言,比如Java和C#都已经提供了这个功能。

语法如下:

class SomeType
{
int number;

public:
SomeType(int newNumber) : number(newNumber) {}
SomeType() : SomeType(42) {}
};

这来自于一个规则:C++03认为一个对象在执行完构建函数之后对象就构建完成了,而C++0x则认为所有构建函数运行完毕才认为对象构建完成。由于大多数构建函数都允许执行,这也就意味着,每个delegate的构建函数要在构建完成的对象上执行。直到基类所有的delegation都完成了,派生类的构建函数才开始执行。

对于基类构建函数,C++0x允许一个类去设定基类构建函数将被继承。也就是说,C++0x编译器将为执行继承以及将派生类forwarding到基类产生相关代码。需要注意,这是一个all-or-nothing特征;要么所有基类的构建函数都被forward或者都不被。而且,对于多重继承还有限制,比如如果两个类如果使用同样签名的构建函数,那么类构建函数就不能从这两个类来继承。在派生类里如果存在和某个基类相匹配的签名,它的构建函数也不可以。

语法如下:

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,而使用整型的那个构建函数将把他初始化为给定的参数值。

5.9 空指针
在1972年C刚诞生的时候,常量0具有双重智能,常数和空指针。这种固有的0两义性带来的歧义被C语言使用预处理宏NULL来解决,其中NULL可以被扩展为((void*)0)或者0。当C++接受了这种行为之后,由于C++里面其他的设计,更多的歧义被引入了进来。从类型void*隐式转换为其他指针类型是不被允许的,所以NULL必须被定义为0,这样才可以让诸如char* c = NULL; 这样的语句被编译通过。糟糕的是,这又影响了函数的重载:

void foo(char *);
void foo(int);

语句foo(NULL); 将会调用foo(int),但这应该不是程序员所希望的。

C++0x通过引入一个显式的null指针新关键字来纠正这个问题:nullptr。nullptr是一种类型,可以隐式转换以及兼容于所有指针类型或者指向成员的指针类型。它不能隐式的转换或者兼容于整型。

为了向后兼容,已存在的0的功能继续保持。如果新的语法成功了,C++委员会就可以宣布将0和NULL作为空指针类型的方式废弃,最终废除这种两义性。

5.10 强类型枚举
标准C++里面,枚举型不是类型安全的。枚举类型实际上是整型,尽管它们是截然不同的。这就使得在两个不同枚举类型的枚举值之间可以进行比较。C++03提供的安全性仅仅在于一个整型或者一个枚举类型的值不能够隐式转换为另外一个枚举型。此外,枚举类型所表示整型的字节数也无法显式的表示出来,它依赖于实现定义。最后,枚举值的作用域是enclosing的范围,所以对于两个独立的枚举类型来说是无法拥有相同的成员名字。

C++0x允许一种特殊的枚举类别,它不会有所有上述的问题。这是通过enum类的声明来实现的:

enum class Enumeration
{
Val1,
Val2,
Val3 = 100,
Val4 /* = 101 */,
};

这个枚举类型是类型安全的。枚举类型的值不能隐式地转换为整型,因此,它们也不能作为整型进行比较(Enumeration::Val4 == 101将会产生一个编译错误)。

关于enum类的隐含类型也被显式的设定了,缺省的,如上面的例子,是int,但也可以按照下面的方法改变:

enum class Enum2 : unsigned int {Val1, Val2};

枚举类型的作用域被定义为枚举名字的作用域。使用枚举名字需要显式地说明作用域。Val1是未定义的,而Enum2:Val1是定义的。

此外,C++0x还允许标准的枚举型用来提供显式的作用域,就好像隐含的类型定义一样:

enum Enum3 : unsigned long {Val1 = 1, Val2};

枚举名字被定义在枚举类型的作用域里面(Enum3::Val1),但为了向后兼容,枚举的名字也可以被放在encloing的范围。

在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 declarations 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.

5.11 尖括号
在任何情况下,标准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.

5.12 显式转换运算符
标准C++增加了explicit这个关键字作为对构建函数的修饰,来阻止单个参数的构建函数用于隐式类型转换的操作。然而,这无法适用于实际的转换运算符上面。例如,一个智能指针类,可以拥有操作符bool()来让它用起来就像原始的指针一样:如果它包含了这种转换,它就可以通过if(smart_ptr_variable)来进行测试(这种情况下,如果指针是非空的返回true,否则返回false)。然而,这也就允许了不期望的转换。由于C++ bool类型被定义为算术类型,它可以被隐式的转换为整型甚至浮点型,从而使得数学操作成为可能,这却不是用户所希望的。

C++0x里面,explicit这个关键字也可以适用于转换运算符(conversion operators)上面。就如同对于构建函数一样,它们阻止了进一步的隐式转换。

5.13 模板类型定义
标准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>;

5.14 透明的内存垃圾回收
C++0x并不直接提供透明的内存垃圾回收机制。相反,C++0x将包括一些特征使得C++里面完成垃圾回收变得容易。

对垃圾回收的完整的支持已经被要求加入到随后的标准或者TR里面。

5.15 没有约束的联合型
在标准C++里面存在对于什么样的对象才能成为union成员是有限制的。比如,union不能包含任何自定义构建函数的对象。对union施加的许多限制似乎是没有必要的,因此在下一个标准里面,除了引用类型之外,所有的关于union成员类型的限制将会取消。这个改变使得union易于使用,更加强大,更加有用。[1]

下面是个可以被C++0x接受的union的简单例子:

struct point
{
point() {}
point(int x, int y): x_(x), y_(y) {}
int x_, y_;
};
union
{
int z;
double w;
point p; // Illegal in C++; point has a non-trivial constructor. However, this is legal in C++0x.
};

这个改变不会影响到现有的代码,因为它们仅仅涉及到对规则的放松。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: