34 区分接口继承和实现继承
公有继承:成员函数的接口总是被继承-------------继承接口和实现
纯虚函数:为了派生类只继承函数接口----继承接口
非纯虚函数=虚函数:提供定义,派生类可以覆写---派生类继承该函数的接口和缺省实现;
总之:纯虚函数,虚函数,非虚函数的差异:
纯虚函数-----只继承接口;
虚函数--------继承接口和一份缺省实现;
非虚函数-----继承接口和一份强制实现
但注意:虚函数的代价,效率成本
35 考虑virtual函数以外的其他选择
目的:派生类重新定义虚函数:
方法:
(1)非虚接口实现模板方法模式(non-virtual-interface NVI)
公有非虚函数调用private非虚函数;
(2)函数指针成员变量实现策略模式
函数指针为类的成员变量(可以初始化为不同的函数)
(3)tr1::function 完成策略模式
typedef std::tr1::function<...>为类的成员变量(派生类可以调用不同函数)
(4)古典的策略模式
虚基类A---各个派生类
虚基类B(内含A)--各个派生类
36 绝不重新定义继承而来的非虚函数
因为:非虚函数--静态绑定
虚函数--动态绑定
公有继承:
1-派生类都是基类;
2-派生类一定会继承基类的接口和实现,
重新定义基类中的函数时---出现矛盾。此时,每个派生类都是一个基类,就不为真了
37:绝不重新定义继承而来的缺省参数值
因为:虚函数动态绑定,而缺省参数---静态绑定
讨论范围:继承一个带有缺省参数值得虚函数。
shape*ps;
shape *pc=new circle;
shape*pr=new rectangle;
三者的静态类型均为:shape*
动态类型:目前所指对象的类型,执行期的行为
分别为:pc的动态类型--circle*,pr的动态类型--rectangle*
ps没有动态类型,因为它尚未指向任何对象。
问题:当同时提供缺省参数给基类和派生类时
基类指针指向派生类时,参数会是基类中的参数
因为;参数是静态绑定
办法:非虚函数中调用虚函数
此时参数,非虚函数都是静态绑定
38 通过符合塑造出has-a或根据某物实现出
复合:一种类型的对象内含其他类型的对象,和public继承完全不同
包括:has-a
根据某物实现出
当复合发生于应用域内的对象间-----has-a
例如:人对象有名称,地址,电话号码等
当发生于实现域内----------is-implemented-in-terms-of
set不能继承list来实现
set:public std::list<T>{}错误
set可以内含list成员来实现
set内含list<T> 正确
39 慎重使用private继承
private继承:
1,派生类对象不会隐式转换为基类对象,void f(基类),f(派生类)错误
2,派生类继承来的成员都变成private
3,意味着根据某物实现,派生类根据基类实现,
派生类具有基类的某些特性,但派生类不为可以当做基类使用的目的,
4,意味着只有实现部分被继承,接口部分略去,
建议:尽可能使用复合,因为继承时,基类变,派生类也变。
private继承实现:
class B:private A{
private:
virtual void tick()const;
...
};
复合实现:public继承+复合
class B{
private:
class temp :public A{
public:
virtual void tick()const;
...
};
temp mytimeer;
};
复合实现相比private继承的优点?
1,可以阻止派生类重新定义tick()
2,将将派生类的编译依存性降至最低。private 时B被编译时A的定义必须可见。
复合实现时--可以内含指针
private继承应用的场合:
1-一个派生类想访问基类的protected成分;
2-或重新定义一个或多个虚函数;
3-空间最优化时【也是Boost中的compress_pair的实现原理】;
空间最优化---类不带任何数据,没有非static成员变量;虚函数,虚基类。
这样的空类对象不使用任何空间,但空类当做另一个类的成员变量时,会增加空间(空间对齐);
而继承此空类不会增加空间。---空白基类最优化
private继承和复合的不同:
1,私有继承意味着根据某物实现,比复合级别低;
2,使用条件不同,三个条件可以使用私有继承,否则使用复合
3,私有继承可以造成空基类最优化。
40慎重使用多重继承
多重继承:容易命名冲突,调用函数时发生歧义。
方法:
1,显示指出调用哪一个基类的函数
2,虚继承 有代价
虚继承:增加大小速度,初始化复杂度等成本,所以,
建议:平时使用非虚继承,使用虚继承时,避免在其中放置数据。
41 隐式接口和编译器多态
面向对象编程---显式接口,运行期多态
模板及泛型编程---隐式接口和编译期多态
模板参数T:必须支持一组隐式接口;
什么是编译期多态?
不同的模板参数具现化会导致调用不同的函数---编译期多态
例如:
template<template T>
void priceee(T&w)
{
if(w.size()>10&&w!=dkkdk)
{
T temp(w);
temp.normalize();
temp.swap(w);
}
}
1,类型T必须支持size,normalize,swap成员函数,拷贝构造函数,
不等比较等,T:必须支持一组隐式接口;
T的隐式接口的约束:
必须提供size等成员函数;必须支持operator!=等
2,涉及w的函数,operator>,!=可能造成模板具现化,发生在编译期。
注意:隐式接口---编译期完成检查
类和模板的区别?
1,都支持接口和多态
2,类--显示接口,以函数签名为中心,多态通过虚函数发生在
运行期;
3,模板参数而言,隐式接口,基于有效表达式
多态通过模板具现化和函数重载解析发生在编译期
42 typename的双重意义
1,template声明式中,class,typename相同;
template<class /typename T> classwidget;
声明模板参数时,二个关键字完全相同;
2,模板内出现嵌套从属名称可能导致解析困难
嵌套从属名称前,必须使用typename关键字,告诉C++是个类型
模板内出现的名称若依赖于某个模板参数,叫做从属名称;
若从属名称在class内呈现嵌套,叫做嵌套从属名称
例如:
T::const_iterator *x;
编译器解释为:乘以x
缺省时嵌套从属名称不是类型,
办法:告诉C++是个类型
前面加上关键字typename
一般规则:模板中涉及一个嵌套从属类型名称,必须在紧邻它的前面加上关键字typename.
3-tynename必须作为嵌套从属类型名称的前缀词的例外是:
typename不能出现在基类列表内的嵌套从属类型名称前,
也不能在成员初值列中作为基类修饰符
例如
template<typename T>
class derived:public base<T>::NESTED{不能有
public :
explicit derived(int x)
:base<T>::NESTED(x)不能有
{
typename base<T>::NESTED temp;必须有
}
};
记住:
声明模板参数时,前缀关键字class,typename可互换;
请使用关键字typename标识嵌套从属类型名称:
但不得在基类列或成员初值列内以它作为基类修饰符。
43 处理模板化基类内的名称
若派生类,基类都是模板时,模板派生类不能调用基类模板类中的函数,无法通过编译,
因为:派生类模板继承的是模板类,有模板参数,无法确切知道基类是什么,无法知道基类是否有个函数。
办法:
1,对特定模板参数偏特化---模板全特化
2,在基类函数调用前加上this->
3,使用using声明式
using基类::函数
4,写出基类资格修饰符【明白指出被调用的函数位于基类内】
基类::函数
44 将与参数无关的代码抽离模板
办法:
相同的代码--放入非模板
模板调用非模板
45 运用成员函数模板接受所有兼容类型
问题:B继承A
但A,B放入智能指针后,不再具有继承关系,完全不同的类
智能类间的转换的方法:对任何类型T,U,SMARTPTR<U>--->smartptr<T>
方法:智能类中使用成员函数模板;
例子:
办法:为smartptr类添加拷贝构造函数模板
拷贝构造函数模板前没有explicit关键字:模仿原始指针,可以隐式类型转换【派生类指针-à基类指针要进行隐式转换】
问题:不希望发生如下转换:
smartptr<int><--àsmartptr<double>
smartptr<基类>---àsmartptr<派生类>
办法:get()函数返回原始指针,
拷贝构造函数模板中,使用成员初值列初始化T*,并以U*作为初值
这样,只有存在某个隐式转换可以将U*指针—》T*指针时,才通过编译
实现:
至此,编译器仍然会为类生成拷贝构造函数(非模板)
为了防止此情况发生:需要编写非模板的拷贝构造函数,【同理:赋值运算符】
实现:
shared_ptr支持来自原始指针,shared_ptr,auto_ptr,weak_ptr的构造,赋值
实现:
46 需要类型转换时请为模板定义非成员函数
将条款24的实现扩充为模板:
和条款24唯一的不同是,rational,operator*变成了模板,但编译就通不过了,
原因:非模板时,编译器知道调用什么函数(就是rational参数的那个operator*)
模板时,编译器不知道调用哪个函数
rational<int>的模板参数T推导为int,
但2不能隐式转换为rational<int>
办法:模板类内使用friend函数;
使得编译器总是能在rational<T>具现化是知道T
或者声明为等价的形式:
现在可以编译了,但无法连接,因为operator*只声明与rational内,没有被定义出来;
类外部的operator*模板提供定义式,但行不通,连接器找不到它
办法:提供定义式
友元模板也可以通过辅助函数实现:
好处是,定义于类内部的函数都是inline,包括operator*这样的friend函数
为了减少inline声明带来的冲击,令operator*不做任何事情,而是调用类外的辅助函数
48 泛型编程---递归求阶乘
模板元编程:编译期得到结果
步骤:
1-递归模板具现化实现循环
template<unsigned n>
struct factorial{
enum{value =n*factorial<n-1>::value};
};
2-模板特化结束循环
template<>
struct factorial<0>{
enum {value=1};
};
调用factorial<n>::value
调用factorial<10>::value