一段C++的读书笔记
给定任意2个类型U和T,你如何来确定U是否继承于T呢?在编译时发现两个类型的这种关系对于泛型库的优化是极为重要的。在泛型函数中,如果某个类实现了特定的接口,你可以根据这种关系为其利用特定的优化算法。另外,如果我们可以在编译期决定2个类的关系,我们也可以远离dynamic_cast,从而避免运行时的效率开销。
在着手解决这个问题之前,我们先来考虑一个更为一般的问题。假设我们有2个任意类型U和T,如何确定T能否自动转换成U呢?
答案也许让你有些惊讶,我们可以利用sizeof来帮忙。你可以把sizeof用在任何复杂的表达式上, sizeof可以返回这个表达式值的大小,而不会在运行时评估表达式的值。这也就意味着,你可以把函数重载、模版实例化、转换规则等等所有你可以在C++表达式中使用的设施统统塞到sizeof中来。实际上,sizeof隐藏了一个可以演绎表达式类型的设施,最终,sizeof会返回表达式结果的类型。
这样我们就可以通过sizeof和重载函数来解决判断类型之间的可转换性的问题。思路很简单:我们提供2个重载函数,一个函数的参数是我们要转换成的类型(我们用U表示),而另一个则用来接收其他所有类型的参数。然后我们把要检测的类型(用T表示)传递给重载函数。如果接受类型U为参数的函数被调用了,我们就认为T可以转换为U,反之则不可以。如何确定哪个函数被调用了呢?我们利用sizeof出马,我们只要让重载函数返回不同的类型,然后检查一下返回值就可以了。
实践一下:
首先,定义2个不同的类型:
class Big { char dummy[ 2 ]; } ;
默认情况下,
sizeof(Small)
是
1
,而
Big
的大小则无关紧要,我们只要知道肯定不是
1
就好了。
其次,定义
2
个重载函数,一个接收要转换成的类型:
Small Test(U);
另一个用来接收“其他的所有类型”,我们要保证在排除所有的转换之后才调用这个函数,
OK,
用省略号表示的参数列表真好满足需求
Big Test(...);
尽管把一个
C++
对象传递给
...
参数类型的函数,其结果未定义,但是实际上我们并没有调用这个函数。我们甚至可以不用实现它。
最后,我们用
sizeof
判断一下就完成任务了:
你也许会说,就是它了!
Test
的调用会创建一个临时对象
T
,之后可能的结果只能是
sizeof(Small)
或
sizeof(Big)
。兴奋之余,我们还要看到一个问题。如果
T
的构造函数被设计成
private
,我们就前功尽弃了。当然解决的方法也很简单,定义一个函数,让他返回类型为
T
的对象。
const bool convExist = sizeof (Test(MakeT())) == sizeof (Small);
最后,把刚才的东西封装到一个类里:
class Conversion {
typedef char Small;
class Big { char dummy[ 2 ]; } ;
static Small Test(U);
static Big Test();
static T MakeT();
public :
enum { exist = sizeof (Test(MakeT())) == sizeof (Small) } ;
} ;
另外,我们还可以设置另外一个常量
Conversion::SameType
,如果
T
和
U
表示同一个类型,那么返回
true
:
class Conversion {
.. as above..
enum { sameType = false }
} ;
之后为同一个类型设计一个偏特化版本:
class Conversion < T, T > {
enum { exists = 1 , sameType = 1 } ;
} ;
最后,回到我们的主题,通过
Conversion
的帮助,我们可以来决定两个类型的继承性了。
(Conversion < const U * , const T * )::exists && \
! Conversion < const T * , const void *> ::sameType)
当
U
继承于
T
或者
U
和
T
是同一个类型的时候,
SUPERSUBCLASS
返回
true
。总结一下,只有下面这
3
种情形
const U*
可以隐式转换到
const T*
:
1.
T
和
U
是同一个类型
2.
T
是
U
的任意一个基类
3.
T
是
void
我们通过第
2
个测试屏蔽了最后一种情形。当然,如果你认为同一种类型也不算是继承关系的话,可以进一步严格其条件:
(SUPERSUBCLASS(T, U) && \
! Conversion( const T, const U)::sameType)