Effective C++ 笔记 (七)

七:模板与泛型编程
条款41:了解隐式接口和编译期多态
面向对象编程总是以显式接口和运行期多态解决问题。Templates及泛型编程与面向对象有根本上的不同,显式接口和运行期多态仍然存在,但重要性降低。反倒是隐式接口和编译期多态更重要。
什么是显式接口,运行期多态,隐式接口,编译期多态?看下面这个例子

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!= someNastWidget){
        Widget temp(w);
        temp.normalize();
        temp.swap(w);
    }
}

显式接口:在上述的例子中,w的类型被声明为Widget,所以w必须支持Widget接口。我们可以在源码中找出这个接口,看看它是什么样子,所以我称此为一个显式接口,也就是它在源码中明确可见。
运行期多态:由于Widget的某些成员函数是virtual,w对那些函数的调用将表现出运行期多态,也就是说将于运行期根据w的动态类型决定究竟调用哪一个函数。
当我们将doProcessing从函数转变成函数模板:

template <typename T>
void doProcessing(T& w){
    if(w.size() >10 && w!= someNastWidget){
        Ttemp(w);
        temp.normalize();
        temp.swap(w);
    }
}

隐式接口:w必须支持哪一种接口,系由template中执行于w身上的操作来决定。本例看来w的类型T好像必须支持size,normalize和swap成员函数、copy构造函数。不等比较(!=,不一定需要,当可以类型转换时,T可以转换为别的类型,并不一定需要自己提供)。这一组表达式便是T必须支持的一组隐式接口。
编译期多态:凡涉及w的任何函数调用,例如operator>和operator!=,有可能造成template具现化,使这些调用得以成功。这样的具现行为发生在编译期。“以不同的template参数具现化function template”会导致调用不同的函数,这便是所谓的编译期多态。

请记住:1.classes和template都支持接口和多态。
2.对classes而言接口是显式的,以函数签名为中心。多态则是通过virtual函数发生于运行期。
3.对template参数而言,接口是隐式的,奠基于有效表达式。多态则是通过template具现化和函数重载解析发生于编译期。

条款42:了解typename的双重意义

嵌套从属名称有可能导致解析困难。举个例子:

template<typename C>
void print(const C& container){
	C::const_iterator* x;
}

这段代码可能有多重意思:1.声明x为一个local变量,它是一个指针,指向一个C::const_iterator。2.如果C有个static成员变量而碰巧被命名为const_iterator,并且x是个global变量,则是const_iterator与x相乘。
如果解析器在template中遭遇一个嵌套从属名称,他便假设这名称不是个类型。
解决办法:任何时候当你想要在template中指涉一个嵌套从属类型名称,就必须在紧邻它的前一个位置放上关键字typename。如

template<typename C>
void print(const C& container){
	typename C::const_iterator* x;
}

请记住:1.声明template参数时,前缀关键字class和typename可互换。
2.请使用关键字typename标识嵌套从属类型名称;但不得在base class lists(基类列)或member initialization list(成员初值列)内以他作为base class修饰符。

条款43:学习处理模板化基类内的名称

template<typename T >
class Derived:public Base<T>{
public:
    using Base<T>::sendClear;  //成立,告诉编译器,请他假设sendClear位于base class内。
    void send(cosnt C% info){
        sendClear(info);//这段代码无法通过编译,无法知道继承的Base<T>具体是什么,也就无法知道他是否有个sendClear函数。
        this->sendClear(info); //成立,假设sendClear将被继承。
        Base<T>::sendClear(info);//成立,但是让人不满意,如果被调用的是virtual函数。明确资格修饰会关闭“virtual绑定行为”
    }
};

请记住:可在derived class template内通过“this->”指涉base class template内的成员名称,或藉由一个明白写出的“base class资格修饰符”完成。

条款44:将与参数无关的代码抽离templates

请记住:1.Templates生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。
2.因非类型模板参数而造成的代码膨胀,往往可以消除,做法是以函数参数或class成员变量替换template参数。
3.因类型参数而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述的具现类型共享实现码。p217

条款45:运用成员函数模板接受所有兼容类型
本条款转载于 https://blog.csdn.net/lkq_primer/article/details/81135152
真实指针做得很好的一件事是,支持隐式转换,继承类指针可以隐式转换为基类指针,指向non-const对象的指针可以转换为指向const对象的指针。
例如有下面的一个三层继承体系中的一些转换:

class Top{};
class Middle:public Top{};
class Bottom:public Middle{};
Top*pt1 = new Middle;
Top*pt2 = new Bottom;
const Top* pct2 = pt1;

但是如果想在用户自定义的智能指针中模拟上述转换,稍稍有点麻烦,我们希望以下代码通过编译:

template<typename T>
class SmartPtr {
public:
	explicit SmartPtr(T* realPtr);
	...
};
SmartPtr<Top>pt1 = SmartPtr<Middle>(new Middle);
SmartPtr<Top>pt2 = SmartPtr<Bottom>(new Bottom);
SmartPtr<Top>pct2 = pt1;

但是同一个template的不同具现体之间并不存在什么与生俱来的固有关系,本例中就是SmartPtr 和SmartPtr、SmartPtr之间并不存在什么关系。编译器视它们为完全不同的class。因此我们需要获得这个转换能力,解决方法是使用成员函数模板;如下:

template<typename T>
class SmartPtr {
public:
	template<typename U>
	SmartPtr(const SmartPtr& other);
	...
};

以上代码的意思是,对任何类型T和任何类型U,这里可以根据SmartPtr生成一个SmartPtr。 我们称此构造函数为泛化拷贝构造函数。

我们希望这个泛化拷贝构造函数能够遵守一定的规则,例如我们不希望父类可以向子类转换,我们也不希望根据SmartPtr去创建一个SmartPtr。我们通过对内置指针提供get和初始化列表来达到这个目的。例如下:

template<typename T>
class SmartPtr {
public:
	SmartPtr(T*p) :heldPtr(p) {}
	template<typename U>
	SmartPtr(const SmartPtr<U>& other):heldPtr(other.get()){ }	
	T* get()const {
		return heldPtr;
	}
private:
	T* heldPtr;
};

这个行为只有当“存在某个隐式转换可将一个U指针转为一个T指针”时才能通过编译,而这正是我们想要的。

请记住

请使用member function template(成员函数模板)生成"可几首所有兼容类型"的函数
如果你声明member template用于"泛化copy构造"或"泛化assignment操作",你还需要声明正常copy构造函数和copy assignment操作符.

条款46:需要类型转换时请为模板定义非成员函数

参考: https://blog.csdn.net/u014038273/article/details/77185919
与条款24相对应。
请记住:当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”。

条款47:请使用traits classed表现类型信息

参考:https://www.cnblogs.com/reasno/p/4802737.html

请记住:1.Traits classes使得“类型相关信息”在编译器可用。他们以templates和“templates全特化”完成实现。
2.整合重载技术后,Traits classes有可能在编译期对类型执行 if…else测试。

条款48:认识template元编程
编译器确保所有源码都有效,纵使是不会执行起来的代码。

请记住:1.template Metaprogramming(TMP,模板元编程)可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率。
2.TMP可以用来生成“基于政策选择组合”的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

二零二三.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值