条款41:了解隐式接口与编译期多态(Understand implicit iterface and compile-time polymorhism)
类和对象:
显示接口(explicit interface): 即在源代码中可见, 可以在头文件内看到类的所有接口(由函数签名式函数名称参数类型返回值等)
运行期多态(runtime polymorphism):成员函数是virtual, 传入类的引用或指针时, 在运行时, 会自动匹配接口, 可能是基类的接口, 也可能是派生类的
模板(templates)和泛型编程(generic programming):
隐式接口(implicit interface):typename T, 在函数中, 所必须支持一组操作, 只有支持这些操作, 才能通过编译
编译期多态(compile-time polymorphism):通过模板(template)的实例化(instantiated)函数模板(function templates), 和重载类似, 都是在编译期绑定
在隐式接口中, 操作符等重载, 已经包含隐式类型转换
如果是程序可以通过编译, 无论是类还是模板, 都要需要通过多态满足接口的要求, 否则无法通过编译.
隐式接口并不基于函数签名式,而是有效表达式组成
template<typename T>
void doProcessing(T& w)
{
if (w.size()>10 && w != someNastyWidget)//看起来w必须提供名为size()函数并且支持运算符重载!=函数(但是这些都不需要满足)
{
T temp(w);
temp.normailze();
temp.swap(w);
}
}
T必须支持size()函数但是这个函数可能从base class 继承而来这个成员函数不需要返回一个整数值,甚至不需要返回一个数值类型他唯一要做的就是返回一个类型为X的对象而X对象加上int(10的类型)必须能够调用一个operator>.同时operator>也不必非得取得一个类型为x参数他也可以取得类型为Y参数,只要存在一个隐式转换将x转换为y
同样的T并不需要支持operator!= 因为这样也是可以得:operator!= 接受一个类型为X的对象和一个类型为Y的对象T可以被转化为X而someNastWidget的类型可被转化为Y这样就可以有效调用operator !=
隐式接口是仅仅有一组有效表达是组成表达式自身自身可能看起来复杂但是他们要求的约束条件相当直接而有明确
if (w.size()>10 && w != someNastyWidget) 必须与bool型兼容
请记住
@ class和template都支持接口和多态
@对于class而言接口是显示的以函数签名为中心,多态通过vitual函数发生于运行期间
@对于template而言接口是隐式的implicit基于有效表达式,多态则是通过template具现化和函数重载解析发生在编译期
条款42 了解typename的双重意义(Understand the two meaning of typename)
1 在C++角度看声明template参数时不论是使用关键字class或typename 意义完全相同
template<class T> class Widget;
template<typename T> class Widget;
2 一定需要使用typename的情况
template<typename T>
void print2nd(const C& container)
{
C::const_iterator iter(container.begin());//会导致解析困难(对于从属名称需加上typename)
++iter;
int value = *iter;
cout << value;
}
C::const_iterator取决于参数C,template内出现的名称如果相依与某个template参数 我们称之为从属名称,如果从属名称在class内成 嵌套状我们称之为嵌套从属名称(C::const_iterator就是)
int 并不依赖与任何template参数我们称为非从属名称
C::const_iterator iter(container.begin());//会导致解析困难,因为C::const_iterator及可能是类型也有可能是static成员变量那样的话(C::const_iterator* x)此时不是定义一个指针而是一个相乘动作
解决方法:
任何时候当你想要在template中指涉一个嵌套从属类型名称你就必须在紧邻他的位置放上typename(但是有例外下面会讲到)
typename C::const_iterator iter(container.begin());
下面看列外情况:
typename 不可以出现在base classes list 内的嵌套从属类型名称之前,也不可以在member initialzation(成员初值列)中作为base class的修饰
例子:
template<typename T>
class Derived :public Base<T>::Nested//base class list中不允许使用typename
{
public:
explicit Derived(int x) :Base<T>::Nested(x)//mem.ini lit 中不允许使用typename
{
typename Base<T>::Nested temp;//既不在base Class list 也不在mem ini list 中使用typename
}
};
通常从属名称类型有点长,一般我们会这样使用
typedef typename std::iterator_traits<IterT>::value_type value_type;
上句意思是使用IterT对象所指物相同类型 如果iterT是 vector<string>::iterator,那么类型就是string
请记住:
@声明template参数时,前缀关键字class和typename可互换
@请使用关键字typename标示嵌套从属类型名称但不得在base class list(基类列)或member initial list(成员初值列)内以他作为base class 修饰符
条款43:学习处理模板化基类的名称(Know how to access names in templatized base class)
假如我们需要写一个程序,他能够传递信息到若干个公司去,信息要么译成密文,要不直接传输,假如我们能够在编译期间就能确定那个信息传到那个公司我们就可以采用基于template
看代码:
class CompanyA
{
public:
void SendCleartext(const std::string& msg);
void SendEncrypted(const std::string& msg);
};
class CompanyB
{
public:
void SendCleartext(const std::string& msg);
void SendEncrypted(const std::string& msg);
};
class Msginfo{};
template<typename Company>
class MsgSender
{
public:
void SendClear(const MsgInfo& info)
{
string msg;
//这儿产生info信息
Company C;
//此处添加将信息写入log
C.SendCleartext(msg);
}
};
在此些类的基础上假如我们需要将每次送出的信息都要写入log我们将会这样实现
template<typename Company>
class LoggingMsgSender:public MsgSender<Company>
{
public:
void SendClearMsg(const MsgInfo& info)
{
//传输前将信息写入log
sendClear(Info)//此处调用基类的函数,这段代码将无法通过编译
//此处添加将信息写入log
}
};
编译器会抱怨SendClear()函数不存在,因为编译器并不知道他继承自什么类(class LoggingMsgSender:public MsgSender<Company> Company是个template参数)不到后来LoggingMsgSender被具体现化就不知道其具体继承自什么类更不知道他是否有这个SendClear函数
为了让问题更加具体化在此我们有个class CompanyZ此类坚持要求信息传输需要加密
class CompanyZ
{
public:
.......
void SendEncrypted(const std::string& msg);
};
我们会发现对于一般的MsgSender template对于CompanyZ并不合适,因为他提供了一个SendClear()函数,此时我们可以将Company产生一个特化版本
template<>//一个全特化
class MsgSender<CompanyZ>
{
public:
void SendSecreat(const MsgInfo& info)
}
我们再此回到
template<typename Company>
class LoggingMsgSender:public MsgSender<Company>
当我们用CompanyZ类来初始化此模板时时就更无法调用sendClear函数了 因为CompanyZ已被特化不含此函数
那就是为什么C++拒绝这个调用(派生类调用基类的函数)原因,它知道base class template有可能被特化而那个特化版本可能不提供和一般性的template相同的接口,因此他往往拒绝templateized base class内寻找继承而来的名称
【问题的解决】
我们有三种方法令C++不进入templatized base classes观察的行为失效有三个办法
1 在base class 函数调用动作之前加上“this->”:
例如:this->sendClear();
2 使用using 声明式
template<typename Company>
class LoggingMsgSender:public MsgSender<Company>
{
public:
using MsgSender<Company>::sendClear;
void SendClearMsg(const MsgInfo& info)
{
//传输前将信息写入log
sendClear(Info)//此处调用基类的函数,这段代码将无法通过编译
//此处添加将信息写入log
}
};
3明白指出被调用的函数位于base class内MsgSender<Company>::sendClear(info);
这是个让人不太满意的做法,对于virtual函数会关闭其绑定行为
请记住
@ 可在dervied class template 内通过this->指涉base class template 内的成员名称 或籍由一个明白写出的“base class” 资格修饰符完成