Programming 版 (精华区)
显式函数模板参数指定
zhangyan译
在这部分,我们将解释显式函数模板参数指定技术如何打破原有的安全的正确代码
。为了避免潜在的问题,新的编程规则需要制定。许多STL的是在新的语言规范制
定前实现的,并且一些实现没有更新过并包含着一些有问题的函数模板,也就是一
个外部函数通过自动模板参数推断调用一个内部的存在问题的函数。
函数模板的类型参数
模板参数的显式指定是一个相对比较新的语言特性,是在C++标准化中加入的。当
你阅读标准制定前的参考资料ARM (Annotated Reference Manual)的时候,你会发
现没有办法来告诉编译器实例化的一个函数模板的参数类型。以前,形如下面所示
的模板是非法的:
template <class T>
T* create();
任何一个函数模板,比如上面的T参数类型,必须被用在函数模板的参数中。否则
,编译器将不能推断出模板参数。在上面的例子中,函数没有任何参数所以编译器
不能确定函数模板实例的类型。
新的语言特性
现在,我们可以显式的通知编译器在函数模板实例中应用何种类型。在上面的例
子中,我们可以用显式参数指定的办法来调用函数,如下
int n = create<int>();
这种create<int>的语言机制就叫做显式函数模板参数指定。这一语法和类模板的
实例化非常相似:模板名字后面有模板参数列表。
即使是在不需要显式函数模板参数指定的情况下,也就是编译器可以自动的推断
出参数类型我们也可以用显式函数模板参数指定的技术避开自动推断。这儿有一个
例子:
template <class T>
void add(T t);
add("log.txt"); // 自动参数推断
add<string>("log.txt"); // 显式参数指定
这个例子也可以说明自动推断和显式指定有着不同的效果。自动参数推断会引发
一个add<char*>的实例产生。而显式指定会产生一个不同的函数实例add<string>
。
陷阱
加入语言的新特性是为了解决模板参数没用用于函数模板参数表中的时候函数模
板实例化的问题。他增加了语言的灵活性,但是这里有一个陷阱。原来的安全代码
可能会出现问题。在参数显式指定之前,利用参数推断实现一个传递参数到其他函
数模板的函数模板是合理的,如下所示:
template <class T>
void inner(T t) ;
template <class T>
void outer(T t)
{ ...
inner(t);
...
}
为什么说现在这是危险,而在显式参数指定之前是安全的呢?这和新加入语言的
特性带来的灵活性有关系。
问题
利用显式参数指定,outer函数可能被实例化为一个引用类型,如下:
class Base;
class Derived : public Base {};
Base& ref = new Derived;
outer<Base&>(ref);
生成的函数outer<Base>可能是这样的:
void outer<Base&>(Base& t)
{ ...
inner(t); // 调用: void inner<Base>(Base t);
...
}
当它调用inner函数模板的时候,它需要参数推断机制,编译器实例化inner函数
模板为Base而不是引用类型Base&。这可能令人惊讶,但却是意料中的:自动参数推
断机制引入了一些隐式的类型转换。一种便是左值到右值的转换。结果是,函数参
数t,一个指向派生类的基类指针,以传值方式从outer函数传入inner函数。派生
类中只有基类的一部分对inner函数有效。这叫做对象切片,这是在许多基类对象
创建过程中出现的一个问题。
解决方法
在一个正确的outer的实现里,我们应该将收到的t传入到inner函数中。这个可以
通过显式模板指定轻松实现,如下:
template <class T>
void inner(T t) ;
template <class T>
void outer(T t)
{ ...
inner<T>(t);
...
}
生成的函数outer<Base&>将可能会用Base&生成一个inner函数模板的实例
void outer<Base&>(Base& t)
{ ...
inner<Base&>(t); // 调用: void inner<Base&>(Base& t);
...
}
函数参数t以引用传值方式传入inner函数,并且没有对象切片发生因为没有副本
产生。
评价
对象切片的问题是在函数模板传递一个基类的引用的时候产生的。现在,你也可
以看出为什么原有的outer和inner实现为什么在显式参数指定加入语言之前是安全
的了:不能用引用类型实例化一个函数模板。因为这个简单的原因,outer的实现
不需要为基类的引用传递做准备。没有对象分片的危险因为没有引入引用。现在,
这一规定已经不复存在,函数模板可以用任何不明确的类型来实例化,包括引用类
型。因此,函数模板的实例必须很好准备着应付混淆。
另一个解决方法
从原理上说,outer函数模板的实现可以采取另外一种方法。也许,他/她不愿意
接受混淆的类型并决定拒绝引用类型来实例化out函数模板,并限定它对值类型的
可用性。这是一个能想到的不接受引用类型实例化的实现:
template <class T>
void inner(T t) ;
template <class T>
void outer(T t)
{ ...
typedef T& dummy;
inner(t);
...
}
对out<Base&>的实例化将会失败,因为C++不允许对引用的引用。生成的函数式像
这个样子的:
void outer<Base&>(Base& t)
{ ...
typedef Base&& dummy; // 错误:引用的引用
inner(t);
...
}
这一实现的缺点是,我们通常会争取模板的最大适用性,而不是限制它的可用性
。除非对数据类型有严格的限制,灵活的使用参数类型显式指定的解决方案会更好
。
模板参数推断中引入的隐式类型转换
为了能被理解,我们指出在自动参数推断中引入的左值到右值的转换并不是在参
数推断中应用的唯一的隐式类型转换。
在模板参数确定前,编译起隐式的做下面的类型转换:
● 左值转换
● 限制转换
● 派生类到基类的转换
C++ Prime对这个方面作了广泛的介绍。
简单而言,编译器丢掉了一些类型信息,比如在我们例子中的左值的引用类型的
属性。比如,编译器用一个数据类型实例化一个函数模板而不是它的引用类型。相
似的,用指针实例化一个函数模板而不是数组类型。他丢弃const限制并且不会实
例化一个const类型的函数模板,但却是non-const类型。
最后一行是自动参数推断引入类型转换,某一特定的类型信息会在编译器确定模
板参数的时候丢失。这些信息可以通过显式的制定函数模板参数来保留。
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:2.723毫秒