创作于:zhihu
C++
本身并没有提供标准的接口(Interface
)类型
所以我们可以有各种方式来实现接口机制
考虑面向对象的范式,C++
提供了纯虚函数,所以我们可以自然地使用抽象类作为接口类型
纯虚函数是以= 0
声明的虚函数(什么怪语法..)
如果一个类内部有纯虚函数,或者继承链上有没被实现的纯虚函数,那么这个类成为抽象类
抽象类对象无法构造,我们只能利用它的引用或指针类型多态,而实际派生出来的类通过继承抽象类并override
纯虚函数来实现接口
一个简单的接口例子
struct IPrintable {
virtual void print() = 0;
};
struct StrangePrinter : IPrintable {
void print() override {
cout << "StrangePrinter::print() hola, amigo!" << endl;
}
};
struct NormalPrinter : IPrintable {
void print() override {
cout << "NormalPrinter::print() hello, world!" << endl;
}
};
int main() {
using PrinterPtr = IPrintable*;
StrangePrinter s;
NormalPrinter n;
PrinterPtr ps = &s, pn = &n;
ps->print();
pn->print();
}
//output:
//StrangePrinter::print() hola, amigo!
//NormalPrinter::print() hello, world!
当然实际中我们不可能每个类只实现一个接口吧,如果是,是不是该考虑解耦了?
C++
支持多重继承,所以我们直接让实际类继承多个抽象类实现多个接口
但是如果多个接口内有相同签名的纯虚函数该如何应对
比如我有一个IHasMinimum<T>
接口,value()
返回T
的最小值,和一个IHasMaximum<T>
接口,value()
返回T
的最大值
然后又有一个IHasBound<T>
,其min(), max()
分别返回T
的最小(大)值,所以让IHasBound<T>
接口去实现IHasMinimum<T>
和IHasMaximum<T>
接口很合理吧
template <typename T> struct IHasMinimum {
virtual T value() = 0;
};
template <typename T> struct IHasMaximum {
virtual T value() = 0;
};
template <typename T>
struct IHasBound : public IHasMinimum<T>, public IHasMaximum<T> {
virtual T min() = 0;
virtual T max() = 0;
// override IHasMinimum::value and IHasMaximum::value ???
}
但是这俩接口的value
签名都是value()
,如果IHasBound
内override
了value
,IHasMinimum
,IHasMaximum
的value()
都会被override
,一个要返回最大值,一个要最小值,不能返回相同的值啊..
template <typename T>
struct IHasBound : public IHasMinimum<T>, public IHasMaximum<T> {
T value() override { /*...*/ }
// override both IHasMinimum::value and IHasMaximum::value
}
也许改个函数名就解决问题了,比如两个value
改名成min_value
,max_value
但也许 1. 我们改不了父类代码 2. 取不出更好的名字 3. 承担不了重命名的痛
如果可以这样分别为IHasMinimum
和IHasMaximum
override
两个版本的value()
就好了
template <typename T>
struct IHasBound : public IHasMinimum<T>, public IHasMaximum<T> {
T IHasMinimum<T>::value() override { /*...*/ }
T IHasMaximum<T>::value() override { /*...*/ }
}
但C++
好像不支持这样的机制,C#
是支持的
所以可以这样曲线救国,我们在IHasBound
和IHasMinimum/IHasMaximum
间分别再加一个新类,新类实现value()
,override
的方法中向下转型获取IHasBound
的this
指针
为了减少垃圾命名,先定义一个模板类
template <typename Interface, typename Struct> struct Impl;
然后这样
template <typename T> struct IHasMinimum {
virtual T value() = 0;
};
template <typename T> struct IHasMaximum {
virtual T value() = 0;
};
template <typename T> struct IHasBound;
template <typename T>
struct Impl<IHasMaximum<T>, IHasBound<T>> : public IHasMaximum<T> {
T value() override { return static_cast<IHasBound<T> *>(this)->max(); }
};
template <typename T>
struct Impl<IHasMinimum<T>, IHasBound<T>> : public IHasMinimum<T> {
T value() override { return static_cast<IHasBound<T> *>(this)->min(); }
};
template <typename T>
struct IHasBound : public Impl<IHasMaximum<T>, IHasBound<T>>,
public Impl<IHasMinimum<T>, IHasBound<T>> {
virtual T min() = 0;
virtual T max() = 0;
};
就好了,一个使用栗子,创建Range<T>
类实现IHasBound
,就可以同时也实现IHasMinimum/IHasMaximum
了
template <typename T> struct Range : public IHasBound<T> {
T _mini, _maxi;
T min() override { return _mini; }
T max() override { return _maxi; }
Range(T mini, T maxi) : _mini{mini}, _maxi{maxi} {}
};
int main() {
Range d(114.514, 1919.810);
IHasMinimum<double> &i1 = d;
IHasMaximum<double> &i2 = d;
IHasBound<double> &i3 = d;
IHasBound<double> *i4 = &d;
std::cout << std::fixed << std::setprecision(3);
std::cout << "IHasMinimum::value(): " << i1.value() << std::endl;
std::cout << "IHasMaximum::value(): " << i2.value() << std::endl;
std::cout << "IHasBound::min(): " << i3.min() << std::endl;
std::cout << "IHasBound::max(): " << i3.max() << std::endl;
std::cout << "IHasBound->min(): " << i4->min() << std::endl;
std::cout << "IHasBound->max(): " << i4->max() << std::endl;
}
//IHasMinimum::value(): 114.514
//IHasMaximum::value(): 1919.810
//IHasBound::min(): 114.514
//IHasBound::max(): 1919.810
//IHasBound->min(): 114.514
//IHasBound->max(): 1919.810
btw,如果实现类不是模板类,以上写法可能会有incomplete type
错误
这时可以使用CRTP
,大概这么写
#include <iostream>
using namespace std;
template <typename Interface, typename Struct> struct Impl;
struct Base1 {
virtual void fun() = 0;
};
struct Base2 {
virtual void fun() = 0;
};
struct child;
template <std::same_as<child> T> struct Impl<Base1, T> : public Base1 {
void fun() override {
cout << static_cast<T *>(this)->name << ", hello from override for Base1" << endl;
}
};
template <std::same_as<child> T> struct Impl<Base2, T> : public Base2 {
void fun() override { cout << static_cast<T *>(this)->name << ", hello from override for Base2" << endl; }
};
struct child : public Impl<Base1, child>, public Impl<Base2, child> {
string name{"iam child"};
};
int main() {
child c;
Base1 &b1 = c;
Base2 &b2 = c;
b1.fun();
// hello from override for Base1
b2.fun();
// hello from override for Base2
//c.fun();
// ERROR: member found by ambiguous name lookup
}