让你编写的类也有类型信息

比如说有这样一个类的继承体系:类CDerivedA和CDerivedB都继承自类CBase。如果要写一个函数传入上面类中的一个对象的引用,在函数里面我们要根据对象的类型来进行相应的处理,也就是说对不同类型的对象进行不同的处理。在这种情况下我们就需要类的对象中保存有类型信息。下面就用一个简单的例子来说明怎么让自己编写的类有类型信息。
    首先,根据类的继承关系创建类型信息tag:

struct lx_Base_tag {};
struct lx_DerivedA_tag : public lx_Base_tag {};
struct lx_DerivedB_tag : public lx_Base_tag {};
     然后,创建一个类型特征结构:

template<typename T>
struct category_traits
{
    typedef typename T::category category;
};
    最后,在每个类里面加上类型信息:

// 为了简单起见,这里的类没有实际内容,只保存类型信息

class CBase
{
public:
    typedef lx_Base_tag category;
};

class CDerivedA : public CBase
{
public:
    typedef lx_DerivedA_tag category;
};

class CDerivedB : public CBase
{
public:
    typedef lx_DerivedB_tag category;
};
    这样,我们就可以在函数中根据对象类型的不同进行不同的处理:

// 为了简单起见,这里不同的处理仅仅为输出不同的字符串

template<typename T>
void Test(T &t)
{
    if (typeid(typename category_traits<T>::category) == typeid(lx_DerivedA_tag))
        cout << "class DerivedA" << endl;

    if (typeid(typename category_traits<T>::category) == typeid(lx_DerivedB_tag))
        cout << "class DerivedB" << endl;
}
    上面的函数虽然实现了想要的功能,但是却不是最优方案。
    根据模板的特性,在上面的例子中类型T在编译期就已经确定。但是,if语句的判断却是在运行期进行判断的。如果出现下面的代码,那么Test就会生成两个副本。

CDerivedA a;
TestA(a);

CDerivedA b;
Test(b);
    这样就会造成编译出的可执行文件膨胀,而且typeid在运行期对效率也有一定的的影响。
    那么有没有什么方法使得在编译期就能根据对象类型的不同而将不同的处理方法编译进可执行文件呢?当然有,那就是函数重载。函数重载的作用就是根据参数类型的不同而调用不同的函数。所以,可以引入一个重载的帮助函数:

// 为了简单起见,这里不同的处理仅仅为输出不同的字符串

template<typename T>
void DoTest(T &t, lx_DerivedA_tag)
{
    cout << "class DerivedA" << endl;    
}

template<typename T>
void DoTest(T &t, lx_DerivedB_tag)
{
    cout << "class DerivedB" << endl;    
}
    而真正的函数只要调用这个帮助函数就行了:

template<typename T>
void Test(T &t)
{
    DoTest(t, typename category_traits<T>::category());
}
    这样,在编译期,所有的类型都确定了,而且根据类型的不同编译器会选择不同的函数编译进可执行文件中。
    通过这种方法,既不会造成编译出的可执行文件膨胀,也避免了typeid在运行期对效率的影响。

 

2008年10月6日更新:
  
    国庆节前参加了公司组织的STL的高级培训,主讲人就是大名鼎鼎的侯捷。他是《STL源码分析》的作者,而这次主讲的内容也是对STL源码的分析。对我来说这也是第一次比较深入的学习STL的源码。而本文中所讲的其实就是STL里面的Traits。STL源码中到处都能看到Traits的身影,这也是实现编译期函数重载的有效方法。
    下面对文中的最后一段代码进行一下解释:
    1.在编译的时候T的类型已经确定;
    2.代码typename category_traits<T>::category()其实就是创建一个类型为T::category的临时变量;
    3.编译期根据上面的那个变量可以推导出这个变量的类型(lx_Base_tag,lx_DerivedA_tag,lx_DerivedB_tag);
    4.通过上面的类型就可以判断调用哪个函数。
    经过上面的几步,就实现了编译期的函数重载。
    另外,对上面2中代码里的typename关键字做一个说明:如果仅仅看category_traits<T>::category这样的代码的话,是分辨不出category是什么的(有可能是一个静态变量),加上typename关键字就是明确的告诉编译器这是一个类型。
posted on 2010-05-31 23:37  carekee  阅读(276)  评论(0)    收藏  举报