对于class而言,接口是显式的,以函数签名式(函数名、参数类型、返回类型)为根基。多态则是通过virtual函数发生在运行期。
如有个类widget:
class Widget
{
public:
Widget(std::string name="default", std::size_t size=0)
:sname(name), ssize(size)
{
}
Widget(std::size_t size=0)
:sname("default"), ssize(size)
{
}
virtual ~Widget(){
}
void print() const{
cout<<"sname: "<sname, other.sname);
std::swap(this->ssize, other.ssize);
}
bool operator!=(const Widget other) const{
return (this->sname != other.sname);
}
private:
std::string sname;
std::size_t ssize;
};
对于template而言,接口是隐式的,以有效表达式为根基。多态则是通过template实例化和函数重载解析发生于编译期。
如有个函数模板:
template
void doProcessing(T& w)
{
if (w.size() > 10 && w != someNastyWidget) {
T temp(w);
temp.normalize();
temp.swap(w);
}
}
凡涉及w的任何函数调用,如operator>和operator!=,有可能造成template实例化使得调用成功。以不同的模板参数T实例化模板将得到并调用不同的函数,这就是编译期多态。
w必须支持哪些接口?系于template中w身上的操作来决定。乍看w的类型T好像要支持size、normalize、swap成员函数、copy构造函数(建立temp)、不等比较操作符。
其实不尽然。
w提供的size()必须返回整数值吗?不一定。诚然w要提供size()(当然也能继承父类得到),但是注意operator被重载的情况,即operator>两旁既可以是数值类型,也可以是其他自定义类型:
bool operator>(const Mem& lhs, const Mem& rhs)
{
return (lhs.mmem > rhs.mmem);
}
现在size()只要返回一个这么一个对象X,它和int 10能够调用重载版本的operator>。如下一种实现,对象X既可以是Mem;也可以是int。同样operator>右边的对象Y既可以Mem;也可以是int,只要Mem支持隐式转换:
struct Mem
{
Mem(int num)
:mmem(num){}
int mmem;
};
#define someNastyWidget 128
Widget wg0("fffff",20);
doProcessing(wg0);
wg0.print();
Widget wg1("fffff",2);
doProcessing(wg1);
wg1.print();
w需要提供成员函数operator!=吗?不一定。同样如下一种实现,operator!=被重载,左操作数或右操作数是WidgetSp,或者能被隐式转换为WidgetSp:
bool operator!=(const WidgetSp& lhs, const WidgetSp& rhs){
return true;
}
class WidgetSp
{
public:
WidgetSp(std::string name="default", const Mem& Memobj=Mem(0))
:sname(name), smem(Memobj)
{
}
WidgetSp(const Mem& Memobj=Mem(0))
:sname("default"), smem(Memobj)
{
}
WidgetSp(int num)
:sname("default"), smem(Mem(num)) //it only canbe convert once, int->Mem->WidgerSp is prohibit
{
}
virtual ~WidgetSp(){
}
void print() const{
cout<<"sname: "<sname, other.sname);
std::swap(this->smem.mmem, other.smem.mmem);
}
private:
std::string sname;
Mem smem;
};
WidgetSp wgs0("shit",3);
doProcessing(wgs0);
wgs0.print();
WidgetSp wgs1("shit",13);
doProcessing(wgs1);
wgs1.print();
结果(注意隐式转换只能被执行一次,从int->Mem->WidgerSp编译器是不认的,因此另外提供构造函数WidgetSp(int num)):
但无论“w.size() > 10 && w != someNastyWidget”涉及什么实际类型,导致什么行为,它都必须与bool兼容。当然除了上面讨论的size、operator!=以外,T必须支持normalize、swap、copy构造函数是没有疑问的。
总之,就像无法以“与class提供显式接口矛盾”的方式使用对象一样,也无法以“不支持template要求的隐式接口”在template中使用一样,两者都通不过编译。