My understanding on C++ traits

Posted by Taolee on August 27, 2017

Talk is cheap, please see the code.

#include <iostream>
#include <string>

template <typename T>
class ClockTraits {
public:
    typedef std::string nameType;
    typedef float periodType;
    nameType getName() const = 0;
    periodType getPeriod() const = 0;
};


class AClock {
public:
    AClock(const char * name, int prd) :
        name(name), prd(prd){
        }
    const char * getName() const {
        return name;
    }
    int getPrd() const {
        return prd;
    }
private:
    const char * name;
    int prd;
};

class Bclock {
public:
    Bclock(const std::string & name, float prd) :
        _name(name), _prd(prd){
    }
    std::string getClockName() const {
        return _name;
    }
    float getPeriod() const {
        return  _prd;
    }

private:
    std::string _name;
    float _prd;
};


template <>
class ClockTraits<AClock> {
public:
    typedef const char * nameType;
    typedef int periodType;
    ClockTraits(const AClock & a) : _clock(a) {}
    nameType getName() {return _clock.getName();}
    periodType getPeriod() { return _clock.getPrd();}
private:
   const AClock & _clock;
};

template <>
class ClockTraits<Bclock> {
public:
    typedef std::string nameType;
    typedef float periodType;
    ClockTraits(const Bclock & b) : _clock(b) {}
    nameType getName() {return _clock.getClockName();}
    periodType getPeriod() { return _clock.getPeriod();}

private:
    const Bclock & _clock;
};

// 为什么需要traits? 代码重用。
//    假如这里的printClock是一种非常复杂的流程和算法,但是我们又不希望
//    printClock 只能应用于某一种类型的clock上面。那么在不依赖于任何clock类型的
//    情况下,怎么写代码?
//
//    1. 直接调用clock对象的成员函数。那么对clock类的要求都寄托在这些成员函数上。
//       如果一个类没有实现这样的成员函数,就不能应用printClock
//
//      template <typename C>
//      void printClock(const C &clock) {
//        std::cout << "Name: " << clock.getName() <<std::endl;
//        std::cout << "Period: " << clock.getPeriod() << std::endl;
//      }
//
//    2. 寄希望于ClockTraits这个类,只要typename C特化了 ClockTraits模版类
//       便能用应用printClock
//
//    1 于 2 的区别在于什么:
//      (1) 1 中printClock直接依赖于clock的类型C,要求C类型具备某些相同的接口。
//            对于代码库中已经拥有的现成的类型, 假设为A B C D,如果A B C D
//            都想应用printClock函数, 要求A B C D都必须有 返回值可以直接<< 的
//            getName() getPeriod()函数,
//            如果其中一个不满足,就无法应用于printClock算法。为了使用必须对
//            A B C D类进行修改。这不满足开闭原则。
//
//            **同样的功能可以用运行期多态实现。即 printClock的原型变为:
//                void printClock( const ClockInterface & clock)
//              每一个应用于printClock的类型都必须时ClockInterface(子)类。**
//
//           2 中printClock 依赖于 模版类 ClockTraits<C>
//              如果A B C D 接口不同,那么给任何一个类定义模版类的特化就好了。
//              不需要修改A B C D类型本身。 只拓展,没修改。
//              如果有新的类型加入, 给新的类型定义一个 ClockTraits 特化,照样可以使用
//              printClock模版类,达到代码的重用。
//
//     1 与 2 各自的优缺点
//         1 可能少写了一些 Traits类的特化的代码,但是1 对应用与其中的类直接做了限制。
//           在不修改已有代码的前提下,无法将模版函数直接应用于多个不同的类。
//            **因此 在没有很多欠账和历史包袱的情况下,用1更写的似乎更快。**
//
//         2 代码量稍多。但是相当于在 模版类型参数C 与 算法之间多了一层隔离,即ClockTraits。
//           也明确的告诉printClock 的潜在用户: 如果你想用我这个模版函数,你的C类型
//           必须满足 ClockTraits 的条件嗷。 
//           ** 在系统中已经有很多各式各样的Clock 类,且不属于同一个继承体系的话。
//              用2实现可以不破坏已有代码。 **

template <typename C>
void printClock(const C &clock) {
    typedef typename ClockTraits<C>::nameType nameType;
    typedef typename ClockTraits<C>::periodType periodType;
    nameType name = ClockTraits<C>(clock).getName() ;
    periodType period = ClockTraits<C>(clock).getPeriod();
    std::cout << "Name: " << name <<std::endl;
    std::cout << "Period: " << period << std::endl;
}

// When I finishes the printClock function and all the above classes
// Someday, I need to add a new clockType to this system, so I can use printClock
// I only need to do is:
//  class NewClockType{
//      ....
//  };
// template <>
// class ClockTraits<NewClockType> {
//    typedef typename someType nameType;
//    typedef typename someOtherType periodType;
//    nameType getName() {...} 
//    periodType getPeriod() {...}
// };
//
//  As long as I define a ClockTraits template specilizaiton for my new class
//  I can use the existing **printClock** template functiona and 
//
//  总的来说,就是,让模版类(函数)(在这里指printClock)依赖于模版(在这里指
//  ClockTraits) 而不是具体类的接口。
//  换一个思路讲, 每一个能调用printClock的类,都在不需要继承的情况下(只需要模版特化)
//  满足了ClockTrats的接口(编译期多态)。

int main() {
    AClock a("clk1", 10);
    Bclock b("clk2", 100);
    printClock<AClock>(a);
    printClock<Bclock>(b);
    return 0;
}