编译期检查class是否有继承关系

来源:互联网 发布:日语同声传译软件 编辑:程序博客网 时间:2024/05/10 23:59

转载至:http://pthread.blog.163.com/blog/static/1693081782010715104330491/

最近看sigc++的代码,看到其中有编译期检测class是否有继承关系的代码,觉得比较精妙,于是拿出来剖析一下

代码中的注释和很多不必要的#ifdef已经被我去掉,该段代码的用法如下,假设有

struct foo{};
struct bar{};

如果foo不是直接或者间接继承自bar,那么is_base_and_drived<bar,foo>::value = false

如果

struct bar{};
struct foo:public bar
{};

那么这种情况下 is_base_and_drived<bar,foo>::value = true

那么这段代码是怎么做到的呢

template <class T_base, class T_derived>
struct is_base_and_derived
{
        private:
                struct big {
                        char memory[64];
                };
                static big  is_base_class_(...);
                static char is_base_class_(typename type_trait<T_base>::pointer);
        public:
                static const bool value =
        sizeof(is_base_class_(reinterpret_cast<typename type_trait<T_derived>::pointer>(0))) ==
                        sizeof(char);
};

template <class T_base>
struct is_base_and_derived<T_base, T_base>
{
        static const bool value = true;
};



观看上面的代码,我们大概可以看到,手法是通过 is_base_class这个重载函数来实现的,通过is_base_class的不同参数,返回不同的类型,而且让返回类型的大小不一样,加以区分。

static big is_base_class(...),接受任意类型的参数,返回struct big,而sizeof(struct big)的大小一般情况下,在这里定义的,是64

static char is_base_class(typename type_trait<T_base>::pointer),接受的则是T_base类型的指针,返回一个 char,sizeof(char)一般情况下应该是1,总之能够跟上面的64明显的区分出来

这里解释一下type_trait是干嘛的,很多时候,我们传的T_base可能是一个class,或者一个,class引用类型,或者一个指针类 型

比如struct foo{},然后这里的T_base可能是foo,也可能是foo&,或者是foo*等等,因此type_trait在这里的作用就是把 T_base真正的类型萃取出来,比如type_trait<foo>::pointer就是foo*,而 type_trait<foo&>::pointer的类型也应该是foo*

因此编译器在某处调用is_base_class的地方,会根据参数来决定调用哪个重载,如果是T_base*的话,会调用返回char的那个重 载,否则就调用返回big的那个重载了。

这里还有问题,因为下面调用is_base_class的地方,

    static const bool value =
               sizeof(is_base_class_(reinterpret_cast<typename type_trait<T_derived>::pointer>(0))) ==
                        sizeof(char);

这里可没有出现T_base*之类的呀,唯一的转换是吧NULL指针,或者0指针,转换成T_drived*啊,这个能起作用吗?能调用到正确的函 数吗?

到这里好好想想,C++的继承体系中,基类的指针可以指向派生类,因此把一个派生类的指针转换成一个基类的指针,是安全的,反之,把一个派生类的指 针,转换成基类指针则是不安全的。

想到这里,我们明白过来了,在这里,用一个派生类的空的指针,当然,万能的编译器是知道T_drived和T_base是不是有派生关系的,于是在 转化成T_drived指针之后,如果T_drived跟T_base是有继承关系的,那么编译器可以顺利的把T_drived*提升成T_base*, 于是找到了返回char的最合适的is_base_class来调用,反之,如果没有继承关系,不能提升,那就只有调用返回big的那个重载了

于是这里value的值也就可以决定了。

template <class T_base>
struct is_base_and_derived<T_base, T_base>
{
        static const bool value = true;
};

这个则是为了语义完备性,因为是is_base_and_drived,因此当base和base作比较的时候,value也应该是true的


原创粉丝点击