转自:http://www.cnblogs.com/liyiwen/archive/2009/12/10/1621451.html
关于类型擦除,在网上搜出来的中文资料比较少,而且一提到类型擦除,检索结果里就跑出很多 Java 和 C# 相关的文章来(它们实现“泛型”的方式)。所以,这一篇我打算写得稍微详细一点。 注意,这是一篇读书笔记(《C++ template metaprogramming》第9.7小节和《C++ テンプレートテクニック》第七章),里面的例子都来自原书。
在 C++ 中,编译器在编译期进行的静态类型检查是比较严格的,但有时候我们却希望能“避过”这样的类型检查,以实现更灵活的功能,同时又尽量地保持类型安全。听起来很矛盾,而且貌似很难办到。但其实 C++ 的库里已经有很多这样的应用了。比如,著名的 boost::function 和 boost::any 。当我们定义一个 function<void(int)> fun 对象,则 fun 即可以存储函数指针,又可以存储函数对象,注意,这两者是不同“类型”的,而且函数对象可以是无限种类型的,但这些不同类型的“东西”都可以存在同一类型的对象 fun 中,对 fun 来说,它关心的只是存储的“对象”是不是“可以按某种形式(如void(int))来调用”,而不关心这个“对象”是什么样的类型。有了 function 这样的库,在使用回调和保存可调用“对象”的时候,我们就可以写出更简单且更好用的代码来。再举一个例子,boost::any 库。any 可以存储任何类型的“对象”,比如 int ,或是你自己定义的类 MyCla 的对象。这样我们就可以使一个容器(比如 vector<boost::any> )来存储不同类型的对象了。
这些库所表现出来的行为,就是这篇文章中要提到的类型擦除,类型擦除可以达到下面两个目的:
- 用类型 S 的接口代表一系列类型 T 的的共性。
- 如果 s 是 S 类型的变量,那么,任何 T 类型的的对象都可以赋值给s。
好了,下面我们具体地看看类型擦除是怎么回事,在这个过程中,我们先以 any 这个类为依托来解释(因为它比较“简单”,要解释的额外的东西比较少)。
any 这个类需要完成的主要任务是:1. 存储任何类型的变量 2. 可以相互拷贝 3. 可以查询所存变量的类型信息 4. 可以转化回原来的类型(any_cast<>)
对于其中,只要说明1和2 ,就能把类型擦除的做法展示出来了,所以,我们这里只实现一个简单的,有1、2、3功能的any类(3是为了验证)。
首先,写个最简单的“架子”出来:
<span style="color:#333333"><span style="color:blue">class </span><span style="color:#010001">my_any </span>{
?? <span style="color:#010001">content_obj</span>;
<span style="color:blue">public</span>:
<span style="color:blue">template </span><<span style="color:blue">typename </span><span style="color:#010001">T</span>>
<span style="color:#010001">my_any</span>(<span style="color:#010001">T </span><span style="color:blue">const</span>& <span style="color:#010001">a_right</span>);
}; </span>
这里,由于 my_any 的拷贝构造函数使用的是模板函数,因此,我们可以任何类型的对象来初始化,并把该对象的复本保存在 content_obj 这个数据成员中。那么,问题是,content_obj 用什么类型好呢?
首先我们会想到,给 class 加个模板参数 T ,然后……,不用然后了,这样的话,使用者需要写这样的代码:
<span style="color:#333333"><span style="color:#010001">my_any</span><<span style="color:#010001">someType</span>> <span style="color:#010001">x </span>= <span style="color:#010001">y</span>;</span>
不同的 y 会创造出不同类型的 x 对象,完全不符合我们要将不同类型对象赋给同一类型对象的初衷。接着,我们会想到用 void *(C 式的泛型手法啊……),但这样的话,我们就会完全地丢失原对象的信息,使得后面一些操作(拷贝、还原等)变得很困难,那么,再配合着加入一些变量用于保存原对象信息?你是说用类似“反射”的能力?好吧,我只好说,我以为 C++ 不存在原生的反射能力,以我浅薄的认识,我只知道像 MFC 式的侵入式手法……,嗯,此路不通。
这个困境的原因在于,在C++ 的类中,除了类模板参数之外,无法在不同的成员(函数、数据成员)之间共享类型信息。在这个例子中,content_obj 无法得知构造函数中的 T 是什么类型。所以类型无法确定。
为了妥善保存原对象复本,我们定义两个辅助类,先上代码(来自 boost::any 的原码):
<span style="color:#333333"><span style="color:blue">class </span><span style="color:#010001">placeholder
</span>{
<span style="color:blue">public</span>: <span style="color:green">// structors
</span><span style="color:blue">virtual </span>~<span style="color:#010001">placeholder</span>() {
}
<span style="color:blue">public</span>: <span style="color:green">// queries
</span><span style="color:blue">virtual const </span><span style="color:#010001">std</span>::<span style="color:#010001">type_info </span>& <span style="color:#010001">type</span>() <span style="color:blue">const </span>= 0;
<span style="color:blue">virtual </span><span style="color:#010001">placeholder </span>* <span style="color:#010001">clone</span>() <span style="color:blue">const </span>= 0;
};
<span style="color:blue">template</span><<span style="color:blue">typename </span><span style="color:#010001">ValueType</span>>
<span style="color:blue">class </span><span style="color:#010001">holder </span>: <span style="color:blue">public </span><span style="color:#010001">placeholder
</span>{
<span style="color:blue">public</span>: <span style="color:green">// structors
</span><span style="color:#010001">holder</span>(<span style="color:blue">const </span><span style="color:#010001">ValueType </span>& <span style="color:#010001">value</span>): <span style="color:#010001">held</span>(<span style="color:#010001">value</span>)
{
}
<span style="color:blue">public</span>: <span style="color:green">// queries
</span><span style="color:blue">virtual const </span><span style="color:#010001">std</span>::<span style="color:#010001">type_info </span>& <span style="color:#010001">type</span>() <span style="color:blue">const </span>{
<span style="color:blue">return typeid</span>(<span style="color:#010001">ValueType</span>);
}
<span style="color:blue">virtual </span><span style="color:#010001">placeholder </span>* <span style="color:#010001">clone</span>() <span style="color:blue">const </span>{
<span style="color:blue">return new </span><span style="color:#010001">holder</span>(<span style="color:#010001">held</span>);
}
<span style="color:blue">public</span>: <span style="color:green">// representation
</span><span style="color:#010001">ValueType held</span>;
}; </span>
首先,定义了一个基类 placeholder ,它是一个非模板的抽象类,这个抽象类的两个接口是用来抽取对保存在 my_any 中的各种类型对象的共性的,也就是,我们需要对被保存在 my_any 中的数据进行拷贝和类型查询。
然后用一个模板类 holder 类继承 placeholder 类,这个(类)派生类实现了基类的虚函数,并保存了相关的数据。注意,派生类的数据成员的类型是 ValueType,也就是完整的原对象类型,由于它是个模板类,各个类成员之间可以共享类模板参数的信息,所以,可以方便地用原数据类型来进行各种操作。
有了这两个辅助类,我们就可以这样写 my_any 了:
<span style="color:#333333"><span style="color:blue">class </span><span style="color:#010001">My_any
</span>{
<span style="color:#010001">placeholder </span>* <span style="color:#010001">content_obj</span>;
<span style="color:blue">public</span>:
<span style="color:blue">template </span><<span style="color:blue">typename </span><span style="color:#010001">T</span>>
<span style="color:#010001">My_any</span>(<span style="color:#010001">T </span><span style="color:blue">const</span>& <span style="color:#010001">a_right</span>):<span style="color:#010001">content_obj</span>(<span style="color:blue">new </span><span style="color:#010001">T</span>(<span style="color:#010001">a_right</span>))
{}
<span style="color:blue">template </span><<span style="color:blue">typename </span><span style="color:#010001">T</span>>
<span style="color:#010001">My_any </span>& <span style="color:blue">operator </span>= (<span style="color:#010001">T </span><span style="color:blue">const</span>& <span style="color:#010001">a_right</span>) {
<span style="color:blue">delete </span><span style="color:#010001">content_obj</span>;
<span style="color:#010001">content_obj </span>= <span style="color:blue">new </span><span style="color:#010001">T</span>(<span style="color:#010001">a_right</span>);
<span style="color:blue">return </span>*<span style="color:blue">this</span>;
}
<span style="color:#010001">My_any</span>(<span style="color:#010001">My_any </span><span style="color:blue">const</span>& <span style="color:#010001">a_right</span>)
: <span style="color:#010001">content_obj</span>(<span style="color:#010001">a_right</span>.<span style="color:#010001">content_obj </span>?
<span style="color:#010001">a_right</span>.<span style="color:#010001">content_obj</span>-><span style="color:#010001">clone</span>() : 0)
{
}
<span style="color:#010001">std</span>::<span style="color:#010001">type_info</span>& <span style="color:#010001">type</span>() <span style="color:blue">const </span>{
<span style="color:blue">return </span><span style="color:#010001">content_obj </span>? <span style="color:#010001">content_obj</span>-><span style="color:#010001">type</span>() : <span style="color:blue">typeid</span>(<span style="color:blue">void</span>);
}
};</span>
现在 my_any 类的 content_obj 的类型定义成 placeholder * ,在构造函数(和赋值运算符)中,我们使用 holder 类来生成真实的“备份”,由于 holder 是模板类,它可以根据赋值的对象相应地保存要我们需要的信息。这样,我们就完成了在赋值的时候的“类型擦除”啦。在 my_any 的 public 接口( type() )中,利用 placeholder 的虚函数,我们就可以进行子类提供的那些操作,而子类,已经完整地保存着我们需要的原对象的信息。
接着我们看下 boost::function 中的 Type Erasure。相比起 boost::any 来,function 库要复杂得多,因为这里只是想讲 boost::function 中的“类型擦除”,而不是 boost::function 源码剖析,所以,我们仍然本着简化简化再简化的目的,只挑着讨论一些“必要”的成分。
我们假设 function 不接受参数。为了更好的说明,我先帖代码,再一步一步解释,注意,下面是一片白花花的代码,几没有注释,千万别开骂,请跳过这段代码,后面会有分段的解释:
<span style="color:#333333"><span style="color:blue">#include </span><span style="color:#a31515"><iostream>
</span><span style="color:blue">#include </span><span style="color:#a31515"><boost/type_traits/is_pointer.hpp>
</span><span style="color:blue">#include </span><span style="color:#a31515"><boost/mpl/if.hpp>
</span><span style="color:blue">using namespace </span><span style="color:#010001">std</span>;
<span style="color:blue">union </span><span style="color:#010001">any_callable </span>{
<span style="color:blue">void </span>(*<span style="color:#010001">fun_prt</span>) (); <span style="color:green">// 函数指针
</span><span style="color:blue">void </span>* <span style="color:#010001">fun_obj</span>; <span style="color:green">// 函数对象
</span>};
<span style="color:blue">template</span><<span style="color:blue">typename </span><span style="color:#010001">Func</span>, <span style="color:blue">typename </span><span style="color:#010001">R</span>>
<span style="color:blue">struct </span><span style="color:#010001">fun_prt_manager </span>{
<span style="color:blue">static </span><span style="color:#010001">R invoke</span>(<span style="color:#010001">any_callable a_fp</span>) {
<span style="color:blue">return reinterpret_cast</span><<span style="color:#010001">Func</span>>(<span style="color:#010001">a_fp</span>.<span style="color:#010001">fun_prt</span>)();
}
<span style="color:blue">static void </span><span style="color:#010001">destroy</span>(<span style="color:#010001">any_callable a_fp</span>) {}
};
<span style="color:blue">template</span><<span style="color:blue">typename </span><span style="color:#010001">Func</span>, <span style="color:blue">typename </span><span style="color:#010001">R</span>>
<span style="color:blue">struct </span><span style="color:#010001">fun_obj_manager </span>{
<span style="color:blue">static </span><span style="color:#010001">R invoke</span>(<span style="color:#010001">any_callable a_fo</span>) {
<span style="color:blue">return </span>(*<span style="color:blue">reinterpret_cast</span><<span style="color:#010001">Func</span>*>(<span style="color:#010001">a_fo</span>.<span style="color:#010001">fun_obj</span>))();
}
<span style="color:blue">static void </span><span style="color:#010001">destroy</span>(<span style="color:#010001">any_callable a_fo</span>) {
<span style="color:blue">delete reinterpret_cast</span><<span style="color:#010001">Func</span>*>(<span style="color:#010001">a_fo</span>.<span style="color:#010001">fun_obj</span>);
}
};
<span style="color:blue">struct </span><span style="color:#010001">funtion_ptr_tag </span>{};
<span style="color:blue">struct </span><span style="color:#010001">funtion_obj_tag </span>{};
<span style="color:blue">template </span><<span style="color:blue">typename </span><span style="color:#010001">Func</span>>
<span style="color:blue">struct </span><span style="color:#010001">get_function_tag </span>{
<span style="color:blue">typedef typename </span><span style="color:#010001">boost</span>::<span style="color:#010001">mpl</span>::<span style="color:#010001">if_</span><
<span style="color:#010001">boost</span>::<span style="color:#010001">is_pointer</span><<span style="color:#010001">Func</span>>, <span style="color:green">// 在VC10中标准库已经有它啦
</span><span style="color:#010001">funtion_ptr_tag</span>,
<span style="color:#010001">funtion_obj_tag
</span>>::<span style="color:#010001">type FunType</span>;
};
<span style="color:blue">template </span><<span style="color:blue">typename </span><span style="color:#010001">Signature</span>>
<span style="color:blue">class </span><span style="color:#010001">My_function</span>;
<span style="color:blue">template </span><<span style="color:blue">typename </span><span style="color:#010001">R</span>>
<span style="color:blue">class </span><span style="color:#010001">My_function</span><<span style="color:#010001">R</span>()> {
<span style="color:#010001">R </span>(*<span style="color:#010001">invoke</span>)(<span style="color:#010001">any_callable</span>);
<span style="color:blue">void </span>(*<span style="color:#010001">destory</span>)(<span style="color:#010001">any_callable</span>);
<span style="color:#010001">any_callable fun</span>;
<span style="color:blue">public</span>:
~<span style="color:#010001">My_function</span>() {
<span style="color:#010001">clear</span>();
}
<span style="color:blue">template </span><<span style="color:blue">typename </span><span style="color:#010001">Func</span>>
<span style="color:#010001">My_function</span>& <span style="color:blue">operator </span>= (<span style="color:#010001">Func a_fun</span>) {
<span style="color:blue">typedef typename </span><span style="color:#010001">get_function_tag</span><<span style="color:#010001">Func</span>>::<span style="color:#010001">FunType fun_tag</span>;
<span style="color:#010001">assign</span>(<span style="color:#010001">a_fun</span>, <span style="color:#010001">fun_tag</span>());
<span style="color:blue">return </span>*<span style="color:blue">this</span>;
}
<span style="color:#010001">R </span><span style="color:blue">operator </span>() () <span style="color:blue">const </span>{
<span style="color:blue">return </span><span style="color:#010001">invoke</span>(<span style="color:#010001">fun</span>);
}
<span style="color:blue">template </span><<span style="color:blue">typename </span><span style="color:#010001">T</span>>
<span style="color:blue">void </span><span style="color:#010001">assign </span>(<span style="color:#010001">T a_funPtr</span>, <span style="color:#010001">funtion_ptr_tag</span>) {
<span style="color:#010001">clear</span>();
<span style="color:#010001">invoke </span>= &<span style="color:#010001">fun_prt_manager</span><<span style="color:#010001">T</span>, <span style="color:#010001">R</span>>::<span style="color:#010001">invoke</span>;
<span style="color:#010001">destory </span>= &<span style="color:#010001">fun_prt_manager</span><<span style="color:#010001">T</span>, <span style="color:#010001">R</span>>::<span style="color:#010001">destroy</span>;
<span style="color:#010001">fun</span>.<span style="color:#010001">fun_prt </span>= <span style="color:blue">reinterpret_cast</span><<span style="color:blue">void</span>(*)()>(<span style="color:#010001">a_funPtr</span>);
}
<span style="color:blue">template </span><<span style="color:blue">typename </span><span style="color:#010001">T</span>>
<span style="color:blue">void </span><span style="color:#010001">assign </span>(<span style="color:#010001">T a_funObj</span>, <span style="color:#010001">funtion_obj_tag</span>) {
<span style="color:#010001">clear</span>();
<span style="color:#010001">invoke </span>= &<span style="color:#010001">fun_obj_manager</span><<span style="color:#010001">T</span>, <span style="color:#010001">R</span>>::<span style="color:#010001">invoke</span>;
<span style="color:#010001">destory </span>= &<span style="color:#010001">fun_obj_manager</span><<span style="color:#010001">T</span>, <span style="color:#010001">R</span>>::<span style="color:#010001">destroy</span>;
<span style="color:#010001">fun</span>.<span style="color:#010001">fun_obj </span>= <span style="color:blue">reinterpret_cast</span><<span style="color:blue">void</span>*>(<span style="color:blue">new </span><span style="color:#010001">T</span>(<span style="color:#010001">a_funObj</span>));
}
<span style="color:blue">private</span>:
<span style="color:blue">void </span><span style="color:#010001">clear</span>() {
<span style="color:blue">if </span>(!<span style="color:#010001">destory</span>) {
<span style="color:#010001">destory</span>(<span style="color:#010001">fun</span>);
<span style="color:#010001">destory </span>= 0;
}
}
};
<span style="color:blue">int </span><span style="color:#010001">TestFun</span>() {
<span style="color:blue">return </span>0;
}
<span style="color:blue">class </span><span style="color:#010001">TestFunObj </span>{
<span style="color:blue">public</span>:
<span style="color:blue">int operator</span>() () <span style="color:blue">const </span>{
<span style="color:blue">return </span>1;
}
};
<span style="color:blue">int </span><span style="color:#010001">main</span>(<span style="color:blue">int </span><span style="color:#010001">argc</span>, <span style="color:blue">char</span>* <span style="color:#010001">argv</span>[])
{
<span style="color:#010001">My_function</span><<span style="color:blue">int </span>()> <span style="color:#010001">fun</span>;
<span style="color:#010001">fun </span>= &<span style="color:#010001">TestFun</span>;
<span style="color:#010001">cout</span><<<span style="color:#010001">fun</span>()<<<span style="color:#010001">endl</span>;
<span style="color:#010001">fun </span>= <span style="color:#010001">TestFunObj</span>();
<span style="color:#010001">cout</span><<<span style="color:#010001">fun</span>()<<<span style="color:#010001">endl</span>;
}</span>
首先需要考虑的是,数据成员放什么?因为我们需要存储函数指针,也需要存储函数对象,所以,这里定义一个联合体:
<span style="color:#333333"><span style="color:blue">union </span><span style="color:#010001">any_callable </span>{
<span style="color:blue">void </span>(*<span style="color:#010001">fun_prt</span>) (); <span style="color:green">// 函数指针
</span><span style="color:blue">void </span>* <span style="color:#010001">fun_obj</span>; <span style="color:green">// 函数对象
</span>};</span>
用来存放相应的“调用子”。另外两个数据成员(函数指针)是为了使用上的方便,用于存储分别针对函数指针和函数对象的相应的“操作方法”。对于函数指针和函数对象这两者,转型(cast)的动作都是不一样的,所以,我们定义了两个辅助类 fun_prt_manager 和 fun_obj_manager,它们分别定义了针对函数指针和函数对象进行类型转换,然后再引发相应的“调用”和“销毁”的动作。
接下来是类的两个 assign 函数,它们针对函数针指和函数对象,分别用不同的方法来初始化类的数据成员,你看:
<span style="color:#333333"><span style="color:#010001">invoke </span>= &<span style="color:#010001">fun_prt_manager</span><<span style="color:#010001">T</span>, <span style="color:#010001">R</span>>::<span style="color:#010001">invoke</span>;
<span style="color:#010001">destory </span>= &<span style="color:#010001">fun_prt_manager</span><<span style="color:#010001">T</span>, <span style="color:#010001">R</span>>::<span style="color:#010001">destroy</span>;
<span style="color:#010001">fun</span>.<span style="color:#010001">fun_prt </span>= <span style="color:blue">reinterpret_cast</span><<span style="color:blue">void</span>(*)()>(<span style="color:#010001">a_funPtr</span>);</span>
当 My_function 的对象是用函数指针赋值时,invoke 被 fun_prt_manager 的 static 来初始化,这样,在“调用”时就把数据成员转成函数指针。同理,可以知道函数对象时相应的做法(这里就不啰嗦了)。
但如何确定在进行赋值时,哪一个 assign 被调用呢?我想,熟悉 STL 的你,看到 funtion_ptr_tag 和 funtion_obj_tag 时就笑了,是的,这里的 get_function_tag 用了 type_traise 的技法,并且,配合了 boost::mpl 提供的静态 if_ 简化了代码。这样,我们就完成了赋值运算符的编写:
<span style="color:#333333"><span style="color:blue"> template </span><<span style="color:blue">typename </span><span style="color:#010001">Func</span>>
<span style="color:#010001">My_function</span>& <span style="color:blue">operator </span>= (<span style="color:#010001">Func a_fun</span>) {
<span style="color:blue">typedef typename </span><span style="color:#010001">get_function_tag</span><<span style="color:#010001">Func</span>>::<span style="color:#010001">FunType fun_tag</span>;
<span style="color:#010001">assign</span>(<span style="color:#010001">a_fun</span>, <span style="color:#010001">fun_tag</span>());
<span style="color:blue">return </span>*<span style="color:blue">this</span>;
}</span>
有了这个函数,针对函数指针和函数对象,My_function 的数据成员都可以正确的初始化了。
如我们所见,在 My_function 中,使用了很多技巧和辅助类,以使得 My_funtion 可以获取在内部保存下函数指针或是函数对象,并在需要的时候,调用它们。函数指针或是函数对象,一旦赋值给 My_funtion,在外部看来,便失去了原来的“类型”信息,而只剩下一个共性——可以调用(callable)
这两个例子已经向你大概展示了 C++ 的“类型擦除”,最后,再补充一下我的理解:C++中所说的“类型擦除”不是有“标准实现”的一种“技术”(像 CRTP 或是 Trais 技术那样有明显的实现“规律”),而更像是从使用者角度而言的一种“行为模式”。比如对于一个 boost::function 对象来说,你可以用函数指针和函数对象来对它赋值,从使用者的角度看起来,就好像在赋值的过程中,funtion pointer 和 functor 自身的类型信息被抹去了一样,它们都被“剥离成”成了boost::function 对象的类型,只保留了“可以调用”这么一个共性,而 boost::any ,则只保留各种类型的“type查询”和“复制”能力这两个“共性”,其它类型信息一概抹掉。这种“类型擦除”并不是真正的语言层面擦除的,正如我们已经看到的,这一切仍然是在 C++ 的类型检查系统中工作,维持着类型安全上的优点。