语言特性
让我们来看看使用C++0x新特性的代码的可能模样:
template<class T> using Vec = vector<T,My_alloc<T>>;
Vec<double> v = { 2.3, 1.2, 6.7, 4.5 };
sort(v);
for(auto p = v.begin(); p!=v.end(); ++p)
cout << *p << endl;
|
在C++98中,除了最后一行代码外其余每一行都是不合法的,而且在C++98中我们不得不编写更多(易犯错误)的代码来完成工作。我希望无需我的解释你就可以猜测到这段代码的含义,不过我们还是逐行看一看。
template<class T> using Vec = vector<T,My_alloc<T>>;
|
在这里,我们定义Vec<T>作为vector<T,My_alloc<T>>的别名。换句话说,我们定义一个名为Vec的标准vector,其工作方式正如我们常用的vector那样,除了它使用我自己定义的配置器(My_alloc)而不是默认的配置器之外。C++中缺乏定义这种别名以及绑定(bind)部分而非全部模板参数的能力。按照传统,这被称为“template typedefs”,因为我们一般采用typedef来定义别名,但出于技术上的原因,我们偏向于使用using。这种语法的优势之一是,它将被定义的名字展示于易被人们发现的显著位置。还要注意另一个细节,我没有像下面这样写:
template<class T> using Vec = vector< T,My_alloc<T> >;
|
我们将不再需要在表示结束符的两个“>”之间添加空格。原则上这两个扩展已经被接受了。
接下来我们定义和初始化一个Vec:
Vec<double> v = { 2.3, 1.2, 6.7, 4.5 };
|
采用一个初始化列表来初始化用户自定义容器(vector<double,My_allocator<double>>)是一种新方式。在C++98中,我们只能将这种初始化列表语法用于聚合体(包括数组和传统的struct)。至于究竟如何实现这种语言扩展仍然在讨论中。最可能的解决方案是引入一种新型构造函数:“序列构造函数”。允许上面的例子可以运作将意味着C++更好地满足其基础设计准则之一:对用户自定义类型和内建类型的支持一样好。在C++98中,数组比vector具有记号上的优势。在C++0x中,情况将不再如此。
接下来,我们对该vector进行排序:
为了在STL的框架内做这件事,我们必须针对容器和迭代器对sort进行重载。例如:
// 使用 < 对容器排序
template<Container C>
void sort(C& c);
// 使用 Cmp 对容器排序
template<Container C, Predicate Cmp>
where Can_call_with<Cmp,typename C::value_type>
void sort(C& c, Cmp less);
// 使用 < 对序列排序
template<Random_ACCESS_iterator Ran>
void sort(Ran first, Ran last);
// 使用 Cmp 对序列排序
template<Random_ACCESS_iterator Ran, Predicate Cmp>
where Can_call_with<Cmp,typename Ran::value_type>
void sort(Ran first, Ran last, Cmp less);
|
这里演示了C++0x目前提议中最具意义的扩展部分(也是有可能被接受的部分):concepts。基本上,一个concept就是一个type的type,它指定了一个type所要求的属性。在这个例子中,concept Container用于指定前两个版本的sort需要一个满足标准库容器要求的实参,where子句用于指定模板实参之间所要求的关系:即判断式(predicate)可以被应用在容器的元素类型上。有了concepts,我们就可以提供比目前好得多的错误消息,并区分带有相同数目实参的模板,例如:
sort(v, Case_insensitive_less()); // 容器与判断式
|
和
sort(v.begin(), v.end()); // 两个随机访问迭代器
|
在concepts的设计中存在的最大的困难是维持模板的灵活性,因此我们不要求模板实参适合于类层次结构或要求所有操作都能够通过虚函数进行访问(就象Java和C#的泛型所做的那样)。在这些语言的泛型中,实参的类型必须是派生自泛型定义中指定的接口(在C++中类似于接口的是抽象类)。这意味着所有的泛型实参都必须适合于某个类层次结构。这将要求部分开发人员在设计的时候就做一些不合理的预设,从而为他们强加一些不必要的约束。例如,如果你编写了一个泛型类,而我又定义了一个类,只有在我知道你指定的接口并将我的类从该接口派生的情况下,人们才可以将我的类用作这个泛型类的实参。这种限制太过严格。
当然对于这种问题总有解决办法,但那会使代码变得复杂化。另一个问题是我们不能直接在泛型中使用内建类型。因为内建类型(例如int)并不是类,也就没有泛型中指定接口所要求的函数——这时候你必须为这些内建类型做一个包装器类,然后通过指针来间接地访问它们。另外,在泛型上的典型操作会被实现为一个虚函数调用。那样的代价可能相当高(相对于仅仅使用简单的内建操作来说,比如+或者<)。以这种方式来实现的泛型,只不过是抽象类的“语法糖”。 |