Home

从is_base_and_derived出发

20 Sep 2014 by LelouchHe

缘由

is_base_and_derivedboost提供的一个用于判断继承关系的工具类,利用is_base_and_derived<B, D>::value的真假,即可判断B/D是否有继承关系

如果不看代码,打死我也是想不出要怎样判断的;看了代码之后,也觉得很模糊.

虽然人家boost体贴的写了很长的注释,但看的云里雾里的,所以查了些资料,试图搞懂它

代码

简化一下实现,大体上如下

template<typename B, typename D>
struct is_base_and_derived {
    static char check(const B *, int); // B版本

    template<typename T>
    static int check(const D *, T);    // D版本

    struct host {
        operator const B *() const;
        operator const D *();
    };

    enum {
        value = (sizeof (int) == sizeof (check(host(), 0)))
    };
};

解释

从最后的结果来看,如果check(host(), 0)调用的是B的版本,就返回false,调用的是D的版本,则返回true.

host()返回一个临时的host对象,直接作为check的参数.check的第一个参数类型是const B *const D *,而host提供了host到这些类型的自定义转换(那两个operator).那么此时,在不同关系下,编译器究竟是如何判断调用哪个函数的呢?

class D : class B

如果此时要调用B版本,我们要经过的类型转换(转换1)大致是:

此时D是继承与B的,所以还有另一个版本的类型转换(转换2):

这两个转换都是针对B版本调用的,初看感觉二者类似,但实际上,根据C++标准,二者还是有优劣

自定义转换 和 标准转换

这2种转换涉及到了2种C++的类型转换方式:

2种转换的基础上,就有了类似上面分析的B版本调用的转换序列:

转换序列的比较(比如重载时的选择),一般是根据标准转换的级别来比较的,对于自定义转换,一般要前后添加必要的”精确匹配”来帮助判断.

序列的等级,以该序列中最高的等级为准

更详细的说明,可见C++标准的13.3.3.1.1和13.3.3.1.2,都有更为精确的说明

B版本调用的选择

此时,再来看B版本调用的2中转换:

看样子好像是转换1更高,但其实不然,转换2的完整顺序其实是:

自定义转换序列中,自定义转换的前后是必须有标准转换的(当然,一旦转型成功,自然就没有了),所以转换1的后面没有精确匹配,但转换2却有

也可以看出来,如果调用B版本的话,是需要用转换2的,即:

如果要调用D版本,我们的转换序列为:

这两个的级别相同(都是1,因为都有精确匹配),但转换次数不一,根据规则,选择较短的

所以当D : B时,实际调用了D版本,check返回的int,自然value=true

D不是B的派生类

此时,转换2是不可行的(最后的指针转换无法成功),所以我们只有:

一个是自定义转换序列,一个是自定义转换,二者是不可比较的(或者说一样好),此时会判断函数类型,普通函数优先于模版函数

所以转换1被选择,调用了B版本,check返回char,value=false

const的重要性

试想,如果host的const B *转换去掉了后面的host,此时:

当D : B时,显然选择转换1,此时value=false

当D不派生自B时,二者一样好,优先普通,还是转换1,此时value=false

有兴趣的同学可以自行实验下

总结

其实这个是C++的一个大坑,即重载函数的选择,除了完全精确匹配非常好认外,其他的都是很坑爹的

我们大致上有这么几步:

  1. 写出每个重载的类型转换序列
  2. 自定义转换序列前后添加适当的精确匹配
  3. 如果都是序列,则比较标准转换的等级,选择较高的
  4. 如果级别一样,则选择较短的
  5. 如果无法比较级别,或级别/长短都一样,则普通函数优于模版函数
  6. 如果到了这里,一般就编译错误吧

我不敢说这个顺序完全对,但大致是可以分析出来的,如果有疑惑,最好还是code一下,用编译器来说话

不过话说,如果你的重载函数真得这么复杂才能分析,我倒是觉得你的设计有问题

后话

貌似”较短”原则不是看长短的,具体的参看C++标准的13.3.3/1,里面有提到过一丝丝.下次如果我有碰到类似的概念,再记录下

不过常见情况下,等级相同看长短也就够了

另.我比照的C++标准是2011年的ISO/IEC 14882:2011,也许和即将出版的C++14的节数不同,大家意会即可

再另.boost的开发者是怎样的脑子啊,这些稀奇古怪的边边角角都能利用到,真乃神人.我等凡人,还是好好的利用大神们提供的工具吧