effective C++ 阅读笔记 4

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







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

多则惑少则明

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值