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>
,也就是這球是一種可比較的球,關係上也比較合理。