EC43

43. 学习处理模板化基类内的名称
  • 如果编译期间有足够信息确定类型,可以采用基于template解法。
    eg:如果要传送消息给多个不同的公司,如果编译期间有足够的信息决定哪个信息传至哪个公司,就可以采用基于template解法。

  • 模板全特化:
    看个例子:

    template<> //该语法象征这既不是template,也不是标准class,而是特化版的MsgSender template,在template实参是CompanyZ时被使用
    class MsgSender<CompanyZ>
    {
    	public:
    	...
    }
    
  • 当一个类A继承模板类B,并在A类中调用模板类的函数,此时编译不会通过。【因为编译器不知道传入的参数是不是上面所说的模板全特化,以至于不知道调用sendClear是不是合法的】
    eg:

    此时有三种方法避免:

    1)在基类函数调用动作之前加入“this->”。即在sendClear之前加上“this->”。【该方法告诉编译器我继承的是模板类的sendClear】

    2)使用using声明式。即在定义sendClearMsg函数之前加个“using MsgSender<Company>::sendClear”【该方法告诉编译器,让他假设sendClear位于基类中】

    3)明白指出被调用的函数位于基类中。即用这种方式调用sendClear:MsgSender<Company>::sendClear(info);

总结:可以在继承类的模板内通过this->指涉基类模板内的成员名称,或者藉由一个明白写出“基类资格修饰符”完成。究根结底,还是因为编译器解析继承类模板定义式时发现指向基类的成员函数无效,所以要采取些行动让编译器知道是指向基类的成员函数还是指向继承类或啥类的。

44.将与参数无关的代码抽离templates
  • templates是节省时间和避免代码重复的一个奇方妙法,虽然我现在还是对templates不太熟悉。
  • templates妙在让编译器具现化要用的函数。如class templates,其成员函数只有在被使用时才被暗中具现化,所有只有在这几个函数的每一个都被使用时才会获得这几个函数,
  • 使用templates缺点:使用不当会导致代码膨胀,虽然源码看起来合身而整齐,但是目标码却膨胀。
  • “共性和变性分析”思想:当编写某个class,若其中某个部分和其他类的某些部分相同,那么就会将共同部分搬到新类中,然后用继承或符合让原先的类取用这共同特性;而原来互异部分仍然留在原位置不动。
  • 编写templates时,也是做相同分析,以相同方式避免重复。但是在template代码,重复是隐晦的,因为只有一个template源码,当其被具现化时才会被发现是否重复了,这时就要自己感受了。(感觉还是挺抽象的)
  • 该条款只讨论由非类型模板参数带来的膨胀,即形如template<typenmae T, size_t n>,当传入的T相同,而n不同时,这时候具现化就会产生代码重复了。这时可以通过以函数参数或class成员变量替换非类型模板参数。

总结:从该条款get到的主要是是要将共同部分的代码抽取出来,方便维护,以及避免代码膨胀。

45. 运用成员函数模板接受所有兼容类型
  • STL容器的迭代器几乎总是智能指针

  • 真实指针(即内置指针)一个很大的优点就是支持隐式转换。eg:继承类指针可以隐式转换为基类指针,指向non-const对象指针可以转换为指向const对象等。
    相应代码例子:

    class Top{}
    class Middle: public Top{}
    class Bottom: public Middle{}
    
    Top *pt1 = new Middle; // 将Middle*转换为Top*
    Top *pt2 = new Bottom; // 将Bottom*转换为Top*
    const Top* pct2 = pt1; // 将Top*转换为const Top*
    
  • 构造模板是member function templates,其作用是为class生成函数。其效用不限于构造函数,还支持赋值操作。

  • 泛化copy构造函数是什么?【感觉就是模板里面嵌套模板。。。。】

    template<typename T>
    class SmartPtr{
        public:
        // 该构造函数根据对象u创建对象t,因为u和t是同一template的不同具现体,所以称为泛化copy构造函数
        // 为了实现约束转换行为,如不能由基类转换为继承类指针,于是模仿智能指针的get成员函数,返回内置类型
        template<typename U>
        SmartPtr(const SmartPtr<U>& other):heldPtr(other.get()){}
        T* get() const{return heldPtr;}
        
        private:
    	T* heldPtr; 
    }
    

    其目的就是可以支持隐式转换。

  • 泛化copy构造函数(是个member function template)是不会改变语言规则,即它不会阻止编译器生成它们自己的copy构造函数。所以如果我们想要控制copy构造函数,那么我们要同时声明泛化copy构造函数和“正常的”copy构造函数。同理,其亦使用于赋值操作。

总结:使用member function templates(成员函数模板)生成“可接受所有兼容类型”的函数。其实就是模板里面嵌套另外一个模板,称其为泛化xxx函数。但若该模板用于泛化copy构造或泛化assignment操作,还是需要声明正常的copy构造函数和copy assignment操作符。

46. 需要类型转换时请为模板定义非成员函数
  • template具现化的实参推导过程中,从不将**“通过构造函数而发生的”隐式类型转换函数纳入考虑**。解决方法:通过在template class内声明为friend函数。

  • 解释该条款:为了让类型转换可能发生在所有实参身上,所以声明其为非成员函数;为了让这个函数被自动具现化,所以要将其声明在类内部;综上,要将其变成friend函数。

  • 在一个class template内,template名称可被用来作为“template和其参数”的简略表达方式,所以在Rational<T>内我们可以只写Rational而不必写Rational<T>

  • 声明class template:(很多编译器会强迫把所有template定义式放进头文件内)

    template<typename T> class Rational;
    

总结:函数模板在实参推导过程中不能为形参调用隐式转换函数,而调用函数则可以使用隐式转换函数。所以可以通过将其变为friend函数转换为调用函数形式,从而执行隐式转换函数。
eg:
不支持隐式转换函数形式

template<typename T>
class Rational
{
    public:
    Rational(const T& numerator =0, const T& denominator = 1);
    ...
};

template<typename T> 
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs){}

支持隐式转换函数形式

template<typename T>
class Rational
{
    public:
    ...
    friend 
        const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs)
    {
        return Rational(lhs.xxx * rhs.xxx, lhs.xxx * rhs.xxx);
    }
};
47. 请使用trait classes变现类型信息
  • STL主要由“用以表现容器、迭代器和算法”的template构成,但也覆盖若干工具性templates,其中一个名为advance,用来将某个迭代器移动某个给定距离。

  • STL的5种迭代器分类:
    1)input迭代器:只能向前移动,一次一步,客户只可读取它们所指东西,只能读取一次
    2)Output迭代器:只能向前移动,一次一步,客户只可涂写它们所指东西,只能涂写一次
    3)forward迭代器:可以读或写所指物一次以上
    4)Bidirectional迭代器:除了可以向前移动,还可以向后移动。如list、set、multiset、map和multimap的迭代器

    5)random access迭代器:可以执行“迭代器算术”,可以在常量时间内向前或向后跳跃任意距离。如vector、deque和string提供的迭代器

  • traits是什么?
    traits是一种技术,是一个C++程序员应该共同遵守的协议,其允许在编译期间取得某些类型信息。并且要求其对内置类型和用户自定义类型的表现必须一样好。
    traits总是被实现为structs,但它们却又往往被称为traits classes

  • typedef:可以typedef用户自定义类型,但是不可以定义指针。

  • iterator_traits为指针指定的迭代器类型是:

    template<typename IterT>
    struct iterator_traits<IterT*>
    {
    	typedef random_access_iterator_tag iterator_category;
    }
    

总结:TR1导入许多新的traits classes用以提供类型消息;Traits classes使得“类型相关信息”在编译期间可用,以templates和“templates特化”完成实现;

整合重载技术后,traits classes有可能在编译期间对类型执行if…else测试。

48. 认识templates元编程
  • TMP(模板元编程)是执行于编译期的过程,其是被发现而不是被发明出来的。
  • TMP的两个伟大效力:
    1)让某些事情变得更容易
    2)将工作从运行期转移到编译期,使C++更高效:较少的可执行文件、较短的运行期、较少的内存需求;但编译时间变长了
  • 运行期是运行到对应代码才发现其是否有效,而编译期是要确保所有源码都有效,即使是不会执行的代码。
  • TMP是个“图灵完全”的机器,意思是它的威力大到足以计算任何事物。并且其是没有真正的循环构件,所有循环效果是由递归完成,其主要是“函数式语言”。
  • 以三个例子论述TMP能达到的目标:
    1)使用TMP可以确保在编译期程序中所有量度单位的组合正确,不论其计算多么复杂。
    2)优化矩阵运算。
    3)可以生成客户定制的设计模式。
    不理解这些例子也没关系,反正只要知道他的最大特点就是:将工作从运行期转移到编译期,大大改善效率。

总结:TMP将工作有运行期转移到编译期,可以更早定位bug,实现早期错误侦测和更高的执行效率。就是有点过于抽象,不好调试+不好理解。

49. 了解new-handler的行为
  • c++受程序员喜欢的原因之一是其允许手工管理内存,C++内存管理例程的主角是分配例程和归还例程,配角是new-handler,这是当operator new无法满足客户的内存需求时所调用的函数。

  • heap是一个可被改动的全局性资源,因此涉及多线程时就要注意可改动的static数据。若没有适当的同步控制,调用内存例程可能很容易导致管理堆的数据结构内容败坏。

  • STL容器所使用的heap内存是由容器所拥有的分配器对象(allocator objects)管理,而不是直接被new和delete直接管理。

  • 当operator new抛出异常来反映一个未获满足的内存需求之前,它会先调用一个客户指定的错误处理函数,即一个所谓的new-handler(处理new异常的方法)。客户必须调用声明在<new>的标准程序库函数:set_new_handler指定这个“用以处理内存不足”的函数。

    namespace std
    {
        typedef void (*new_handler)();
        new_handler set_new_handler(new_handler p) throw();
    }
    
  • 如何使用set_new_handler?
    set_new_handler是以new_handler为参数和返回值的,而new_handler是一个指向空参数和空返回值的函数指针,所以可以自己定义这样的函数指针:

    void outOfMem()
    {
        std::cerr<<"Unable to satisfy request for memory\n";
        std::about();
    }
    
    int main()
    {
        std::set_new_handler(outOfMem);
        int *pBigDataArray = new int[111111111];
    }
    
  • 一个设计好的new-handler函数必须做的事情:
    1)让更多内存可被使用。即程序一开始执行就分配一大块内存,当new-handler第一次被调用,将他们释放给程序使用。【所以是如果new过程有问题,那么就把这大块内存释放掉就好】
    2)安装另一个new-handler。若当前new-handler无法取得更多可用内存,可以调用新new-handler,令其修改“会影响new-handler行为”的static数据、namespace数据或global数据。
    3)卸载new-handler。就是把null指针传给set_new_handler,当没有安装任何new-handler,那么operator new会在内存分配不成功时抛出异常。
    4)抛出bad_alloc(或派生自bad_alloc)的异常。该异常不会被operator new捕捉,因此会被传播到内存索求处。
    5)不返回。通常调用abort或exit。

  • c++不支持class专属的new-handler,也就是说,new-handler函数要是全局函数,即用static修饰。

  • nothrow形式:分配失败便返回null行为。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Ta5UvJk-1569503305794)(C:\Users\panbaoru\AppData\Roaming\Typora\typora-user-images\1569381884697.png)]

    使用new nothrow创建的内存给widget,此时Widget得到了内存就可以执行它的构造函数,但是我们又不知道构造函数里面是否有调用new,以及其是否是用new nothrow,导致构造函数里面的new可能会导致异常,所以并没有运用nothrow new的需要

总结:set_new_handler允许客户指定一个函数(其格式是参数和返回值均为void),在内存分配无法获得满足时被调用。nothrow new是个颇为局限的工具,因为他只适用于内存分配,不能保证后继的构造函数调用还是可能抛出异常。

50. new和delete的合理替换时机
  • 替换编译器提供的operator new或operator delete的理由:
    1)用来检测运用上的错误。
    2)为了强化效能。自带的new会导致破碎问题,导致程序无法满足大区块内存要求,即使有时总量足够但分散为许多小区块的自由内存。
    3)为了收集使用上的统计数据。因为不知道软件更倾向于使用FIFO还是LIFO形式分配和归还内存。
    4)为了增加分配和归还的速度。
    5)为了降低空间额外开销
    6)为了弥补非最佳齐位
    7)为了将相关对象成簇集中
    8)为了获得非传统的行为,如分配和归还共享内存。

总结

当遇到这些理由时候,可以考虑写个自己的new和delete。

51. 编写new和delete时要固守常规
  • 实现一致性operator new必须返回正确的值,内存不足时必须调用new-handling函数,必须有对付零内存需求的准备。
  • operator new的返回值:如果可以有能力供应客户申请的内存,就返回一个指针指向那块内存;若没能力,就抛出bad_alloc异常。

总结

该条款教我们咋写自己的new和delete。关于new,operator new应该内含一个无穷循环,并在其中尝试分配内存,若无法满足内存需求,则该调用new-handler,并且其要有能力处理0bytes申请。

关于delete,delete应该在收到null指针不做任何事情。

52. 写了placement new也要写placement delete
  • 如果operator new接受的参数除了一定会有的size_t之外还有其他,则成为placement new。如:|

    void *operator new(std::size_t, void *pMemory) throw();
    
  • 如果运行期系统要取消operator new的分配并恢复旧观,那么运行期系统就会寻找“参数个数和类型都与operator new相同”的某个operator delete。【即new有什么后面加的参数,相应的delete也要有相应的参数,这样才配套】
    eg:

    class Widget{
        public:
        ...
        static void * operator new(size_t size, ostream& logStream) throw(bad_alloc);
        static void operator delete(void *pMemory) throw();
        static void operator delete(void *pMemory, ostream& logStream);
        
        // 在该例子中, 【ostream& logStream】 该参数就是新增的 所以delete也要有该参数
    }
    

    用例:

    Widget *pb = new (std::cerr) Base; // 可以省去size的参数输入
    
  • 由于存在命名空间遮掩的情况,所以如果基类有写placement new,那么此时就不能用标准new方式new该类了,要避免这种情况,则需要建立一个基类,其内含所有正常形式的new和delete。

  • 要想以自定义形式扩充标准形式的客户,可以利用继承机制和using 声明式。

总结

当我们写一个placement operator new,那么也要对应写一个placement operator delete,以免出现无意造成的内存泄漏。并且还要考虑命名空间遮蔽情况,为了要能使用标准new创建该头像,要在基类含有正常形式的new和delete。

53. 不要轻忽编译器的警告

emm现在编写都是忽视warning,没有error就万事大吉了。。。。。不过编译器的warning可以预测我们运行时结果和预期不符的bug,所以还是得瞅瞅warning,不改也起码心里有个底。

54-55. TR1和BOOST

TR1是一份规范,其实物是boost,两个都包含很多东西,如TR1有智能指针、正则表达式、一般化函数指针等,boost还包括泛型编程,覆盖一大组traits classes和模板元编程等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值