模版与继承


C++ 可以定义类模版,在继承时对象也可以使用模版,不过并不鼓励这种做法,例如,你也许会想要量测某个方法的执行时间,为了可以重用量测用的代码,或许会采用这样的设计:

#include <iostream>
#include <chrono>
using namespace std;

unsigned epoch() {
    return chrono::system_clock::now()
              .time_since_epoch().count();
}

template <typename T>
class Timing : public T {
public:
    using T::T;

    void execute() {
        unsigned begin = epoch();
        T::execute();
        cout << epoch() - begin << endl;
    }
};

class EmptyStringOutputter {
public:
    void execute() {
        for(int i = 0; i < 100000000; i++) {
            cout << "";
        }
    }
};

int main() { 
    Timing<EmptyStringOutputter> emptyStringOutputter;
    emptyStringOutputter.execute();

    return 0;
}

Timing继承的对象,必须具有execute方法,虽说这样可以达成目的,然而,模版实例化的Timing<EmptyStringOutputter>实际上会像是:

class TimingXXX : public EmptyStringOutputter {
public:
    using EmptyStringOutputter::EmptyStringOutputter;

    void execute() {
        unsigned begin = epoch();
        EmptyStringOutputter::execute();
        cout << epoch() - begin << endl;
    }
};

这就要问了,TimingXXX是一种EmptyStringOutputter吗?或者Timing<EmptyStringOutputter>是一种EmptyStringOutputter吗?Timing<EmptyStringOutputter>就阅读上,应该是有一个(has-a)EmptyStringOutputter吧!实际上这个需求,可以用组合(composite)来达成,而不是使用继承:

#include <iostream>
#include <chrono>
using namespace std;

unsigned epoch() {
    return chrono::system_clock::now()
              .time_since_epoch().count();
}

template <typename T>
class Timing {
    T ⌖
public:
    Timing(T &target) : target(target) {}

    void execute() {
        unsigned begin = epoch();
        this->target.execute();
        cout << epoch() - begin << endl;
    }
};

class EmptyStringOutputter {
public:
    void execute() {
        for(int i = 0; i < 100000000; i++) {
            cout << "";
        }
    }
};

int main() { 
    EmptyStringOutputter emptyStringOutputter;
    Timing<EmptyStringOutputter> timing(emptyStringOutputter);
    timing.execute();

    return 0;
}

这么一来,Timing<EmptyStringOutputter>就实现与阅读上,就都是有一个(has-a)EmptyStringOutputter了。

结合模版与继承时比较合理的一个例子,是在想共用某个相同实现之时,例如:

#include <iostream>
using namespace std;

template <typename T>
class Comparable {
public:
    virtual int compareTo(T that) = 0;

    bool lessThan(T that) {
        return compareTo(that) < 0;
    }

    bool lessOrEquals(T that) {
        return compareTo(that) <= 0;
    }

    bool greaterThan(T that) {
        return compareTo(that) > 0;
    }

    bool greaterOrEquals(T that) {
        return compareTo(that) >= 0;
    }    

    bool equals(T that) {
        return compareTo(that) == 0;
    }

    virtual ~Comparable() = default;
};

class Ball : public Comparable<Ball> {
    double radius;

public:
    Ball(double radius) : radius(radius) {}

    int compareTo(Ball that) override {
        return this->radius - that.radius;   
    }
};

int main() { 
    Ball b1(100);
    Ball b2(50);

    cout << b1.greaterThan(b2) << endl;

    return 0;
}

在这个例子中,Comparable实现了部份用于比较的方法,实际上如何比较对象的状态并不知道,毕竟不同类的定义不同,因此以模版参数T代表对象类型,并规范了compareTo必须由子类实现。

Ball类继承时将模版实例化为Comparable<Ball>Ball只要重新定义compareTo,就可以使用事先实现的lessThan等方法了,在这种情况下,Ball是一种Comparable<Ball>,也就是这球是一种可比较的球,关系上也比较合理。


展开阅读全文