20180328 C++ 了解隐式接口和编译期多态
const有一种使用情况是:
修饰类成员函数:用const修饰的类成员函数,在该函数体内不能修改该类对象的任何成员变量,也不能调用类中任何非const成员函数,eg:
class A
{
public:
int& getValue() const
{
//a = 10; //错误,不能修改
return a; //正确
}
private:
int a; //非const成员变量
}
下面进入正题:
面向对象的编程世界总是以显示接口(explicit interfaces)和运行期多态(runtime polymorphism)解决问题,例如下面的类1和下面的函数2:
//类1(这个类其实没有明确的意思)
class Widget{
public;
Widget();
virtual ~Widget();
virtual std::size_t size() const;
virtual void normalize();
void swap(Widget& other);
...
};
//函数(这个函数其实也没有明确的意思)
void doProcessing(Widget& w)
{
if(w.size() > 10 && w != someNastyWidget)
{
Widget temp(w);
temp.normalize();
temp.swap(w);
}
}
这样doProcessing里的w就有如下特点:
1)由于w的类型被声明为Widget,所以w必须支持Widget接口。即我们可以在源码中找到这个接口(如在Widget的 .h文件中),看看它到底是什么样的,所以我们称此为显示接口(explicit interface),也就是它在源码中明确可见;
2)由于Widget的某些成员函数是虚的,w对那些函数的调用将表现出运行期多态(runtime polymorphism),也就是说将于运行期根据w的动态类型决定究竟调用哪一个函数。
而在模板(Templates)和泛型编程的系统中,其与面向对象的性质完全不同。在该系统中,显示接口和运行期多态仍然存在,但重要性降低了。反倒是隐式接口(implicit interfaces)和编译器多态(compile-time polymorphism)显得尤为重要。下面我们将doProcessing()从函数转变成函数模板(function template):
template<typename T>
void doProcessing(T& w)
{
if(w.size() > 10 && w != someNastyWidget)
{
T temp(w);
temp.normalize();
temp.swap(w);
}
}
现在doProcessing里的w有如下性质:
1)w必须支持哪一种接口是由模板(template)中执行于w身上的操作来决定的,本例中可以支持size、normalize和swap成员函数、拷贝构造函数(用以建立temp)、不等比较(inequality comparison,用来比较someNasty-Widget),但这些并非完全正确,但目前而言挺真实的,最重要的是这一组表达式(对此模板而言必须有效编译)是T必须支持的一组隐式接口(implicit interface)。
2)但凡涉及w的任何函数调用(如operator>或operator=)都有可能造成模板具现化(instantiated),使得这些调用得以成功。这样的具现化行为发生在编译器。“通过不同的模板参数具现化的函数模板”会导致调用不同的函数,这就是所谓的编译期多态(compile-time polymorphism)。
模板的“运行期多态”和“编译期多态”之间的差异 类似于“哪一个重载函数该被调用(发生在编译期)”和“哪一个虚函数该被绑定(发生在运行期)”之间的差异。
而显示接口和隐式接口的差异就不好理解了,下面详细说明:
通常显示接口由函数的签名式(函数名+参数类型+返回类型)构成,如Widget类:
class Widget
{
public:
Widget();
virtual ~Widget();
virtual std::size_t size() const;
virtual void normalize();
void swap(Widget& other);
};
其公有接口由一个构造函数、一个析构函数、size()函数、normalize()函数、swap()函数及参数类型、返回类型、常量性(constnesses)构成。当然也包括编译器产生的拷贝构造函数、和拷贝赋值操作符。另外也可以包括typedefs,也可以包括 我们不提倡的公有成员变量(我们建议成员变量都设为私有的)。
而隐形接口就完全不同了,它并不基于函数签名式,而是由有效表达式(valid expressions)组成,再来看一下doProcessing模板吧:
template<typename T>
void doProcessing(T& w)
{
if(w.size() > 10 && w != someNastyWidget)
{
...
}
}
T(w的类型)的隐式接口似乎有这样的约束:
1)它必须提供一个名为size的成员函数,该函数返回一个整数值;
2)它必须支持一个operator!= 的函数,用来比较两个T对象,在这里我们假设someNastyWidget的类型为T。
由于操作符带来的可能性,这两个约束都不需要满足。
/*************************没太看懂*****************************/
注意:
1)里与模板都支持接口(interfaces)和多态(polymorphism);
2)对于类而言接口是显性的(explicit),以函数签名为中心,多态则是通过虚函数发生于运行期。
3)对于模板参数而言,接口是隐形的(implicit)以有效表达式为中心,多态则是通过模板具现化和函数重载解析(function overloading resolution)发生于编译期。