• 条款46请输入转换的时候,需要定义非模板成员函数
  • 条款47请使用traits class表现类型信息

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

条款 24提到过为什么non-member函数才有能力“在全部实參身上实施隐式类型转换”。本条款接着那个Rational样例来讲。把Rational class模板化

    template<typename T>class Rational{public:Rational(const T& numerator=0,const T& denominator=1);const T numerator() const;const T denominator() const;……};template<typename T>const Rational<T> operator*(const Rational<T>& lhs,const Rational<T>& rhs){……};Rational<int> oneHalf(1,2);Rational<int> result=oneHalf*2;//错误,无法通过编译

非模板的样例能够通过编译。可是模板化的样例就不行。在*条款**24,编译器直到我们尝试调用什么函数(就是接受两个Rational參数那个operator ),可是这里编译器不知道。编译器试图想什么函数被命名为operato* 的template详细化出来,它们知道自己能够详细化某个operator* 并接受两个Rational參数的函数。但为完毕这一详细化行动,必须先算出T是什么。问题是它们没这个能耐。

看一下这个样例。编译器怎么推导T。

本例中类型參数各自是Rational和int。operator* 的第一个參数被声明为Rational。传递给operator* 的第一实參(oneHalf)正类型正是Rational。所以T一定是int。operator* 的第二个參数类型被声明为Rational。但传递给 operator* 的第二个实參类型是int,编译器怎样推算出T?也许你期望编译器使用Rational的non-explicit构造函数将2转换为Rational,进而推导出T为int,但它不这么做,由于在template实參推导过程中从不将隐式类型转换考虑在内。

隐式转换在函数调用过程中的确被使用,可是在能够调用一个函数之前,首先要知道那个函数的存在。为了知道存在,必须先为相关的function template推导出參数类型(然后才干够将适当的函数详细化出来)。

可是在template实參推导过程中不考虑通过构造函数发生的隐式类型转换。

如今解决编译器在template实參推导方面遇到的挑战,能够使用template class内的friend函数。由于template class内的friend声明式能够指涉某个特定的函数。也就是说class Rational能够说明operator* 是它的friend函数。class templates并不依赖template实參推导(后者仅仅施行于function templates身上),所以编译器总是能够在class Rational详细化时得知T。

所以令Rational class声明适当的operator*为friend函数,能够简化整个问题。

 template<typename T>class Rational{public:……friend const Rational operator*(const Rational& lhs,const Rational& rhs);//声明};template<typename T>const Rational<T> operator*(const Rational<T>& lhs,const Rational<T>& rhs)//定义{……};

这时候对operator* 的混合调用能够通过编译了。oneHalf被声明为一个Rational,class Rational被详细化出来。而作为过程的一部分,friend函数operator* (接受Rational參数)也就自己主动声明出来。

后者身为一个函数而非函数模板,因此编译器在调用它的时候使用隐式转换(将int转换为Rational)。所以混合调用能够通过编译。尽管通过编译,可是还会有链接问题,这个稍后再谈。先来看一下Rational内声明operator *的语法。

在一个class template内,template名称可被用来作为template和其參数的简略表达方式,所以在Rational内,我们能够把Rational简写为Rational。

假设像以下这样写。一样有效

    template<typename T>class Rational{public:……friend const Rational operator*(const Rational<T>& lhs,const Rational<T>& rhs);//声明};

如今回头看一下刚刚说的链接的问题。

尽管编译器直到我们调用的函数是接受Rational的那个operator * ,可是这个函数仅仅有声明,未定义。我们本来想让此class外部的operator * 提供定义式,可是这样行不通。假设我们自己声明了一个函数(Rational template内的作为),就有责任定义那个函数。假设未定义。链接器就找不到它。一个最简单的办法就是将operator * 的定义合并到其声明内:

    template<typename T>class Rational{public:……friend const Rational operator*(const Rational& lhs,const Rational& rhs);//声明+定义{return Rational(lhs.numerator()*rhs.numerator(),lhs.denominator()*rhs.denominator());}};

这个技术尽管使用了friend,却与传统的friend用途“訪问class的non-public成员”不同。

为了让类型转换可能发生与全部实參身上。我们须要一个non-member函数(**条款**24)。为了让这个函数被自己主动详细化,我们须要将它声明在class内部;而在class内部声明non-member函数的唯一办法就是让它成为一个friend。

定义在class内部的函数都是inline函数。包含像operator * 这种friend函数。为了将inline声明带来的冲击最小化。能够让operator * 调用定义在class外部的辅助函数。

    template<typename T> class Rational;//forward decelariontemplate<typename T>const Rational<T> doMultiply(const Rational<T>& lhs,const Rational<T>& rhs);template<typename T>class Rational{public:……friend const Rational operator*(const Rational& lhs,const Rational& rhs);//声明+定义{return doMultiply(lhs,rhs);}};

很多编译器会强迫你把template定义式放到头文件,所以有时你须要在头文件定义doMultiply

    template<typename T>const Rational<T> doMultiply(const Rational<T>& lhs,const Rational<T>& rhs){return Rational<T>(lhs.numerator()*rhs.numerator(),lhs.denominator()*rhs.denominator());}

doMultiply是个template,自然不支持混合乘法,事实上也不是必需支持。它仅仅是被operator * 调用,operator * 支持了混合乘法。

总结

  • 当编写一个class template时,它所提供之“与此template相关的”函数支持“全部參数之隐式类型转换”时,请将那些函数定义为class template内部的friend函数。

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

STL主要由容器、迭代器和算法的templates构成,也包含若干工具性templates。当中有一个advance用来将迭代器移动某个给定距离:

    template<typename IterT, typename DistT>void advance(IterT& iter, DistT d);//d大于零。向前移动,小于零则向后移动

表面上看,仅仅是iterate+=d的动作,可是迭代器有5中。仅仅有random access(随机訪问)迭代器才支持+=操作。其它类型没这么大威力。仅仅有重复++和–才行。

STL源代码中关于迭代器的部分能够參考这里。这里也回想一下这5中迭代器。

  • input迭代器。它是read only,仅仅能读取它指向的对象,且仅仅能读取一次。它仅仅能向前移动。一次一步。

    它模仿指向输入文件的阅读指针(read pointer);C++程序中的istream_iterators就是这类的代表。

  • output迭代器,和input迭代器相反。它是write only。

    它也是仅仅能向前移动,一次一步。且仅仅能涂写一次它指向的对象。它模仿指向输出文件的涂写指针(write pointer);ostream_iterators是这一类代表。

  • forward迭代器。这个迭代器派生自input迭代器,所以有input迭代器的全部功能。而且他能够读写指向的对象一次以上。
  • bidirectional迭代器继承自forward迭代器,它的功能还包含向后移动。

    STL中的list、set、multiset、map、和multimap迭代器就是这一类迭代器。

  • random access迭代器继承自bidirectional迭代器。它厉害的地方在于能够向前或向后跳跃随意距离,这点相似原始指针,内置指针就能够当做random access迭代器使用。vector、deque和string的迭代器就是这类。

这5中分类。C++标准程序库提供专属卷标结构(tag struct)加以确认:

    struct input_iterator_tag {};  struct output_iterator_tag {};  struct forward_iterator_tag : public input_iterator_tag {};  struct bidirectional_iterator_tag : public forward_iterator_tag {};  struct random_access_iterator_tag : public bidirectional_iterator_tag {};

在了解了迭代器类型后,我们该去实现advance函数了。实现要高效。对于random access迭代器来说,前进d距离要一步完毕。而其它类型则须要重复前进或后退

    template<typename Iter, typename DistT>void advance(IteT& iter,DistT d){if(iter is a random access iterator)iter+=d;else{if(d>=0)while(d--) ++iter;else while(d++) --iter;}}

在上面实现中要推断iter是否为random access迭代器。即要知道IterT类型是否为random access类型。这就须要traits,它同意我们在编译期间获取某些类型信息。traits是一种技术,是C++程序猿共同遵守的协议。

这个技术要求之中的一个就是,它对内置类型和自己定义类型表现的一样好。traits必须能够施行于内置类型。意味着“类型内的嵌套信息”这种东西出局了,由于我们无法将信息嵌套于原值指针内。

所以类型的traits信息必须位于类型自身之外。标准技术是把它放进一个template及其一个或多个特化版本号中。这种templates在STL中有若干个,迭代器的为iterator_traits:

    template<typename IterT>//用来处理迭代器分类struct iterator_traits;

尽管iterator_traits是个struct,往往称作traits classes。其运作方式是,针对每个类型IterT,在struct iterator_traits内声明某个typedef命名为iterator_category,用来确认IterT的迭代器分类。iterator_traits以两个部分实现上述所言。

1、它要求用户自己定义的迭代器嵌套一个typedef,名为iterator_category。用来确认是哪个卷标结构(tag struct),比如deque和list

 template<typename T>class deque{public:class iterator{public:typedef random_access_iterator_tag iterator_category;……};……};template<typename T>class list{public:class iterator{public:typedef bidirectional_iterator_tag iterator_category;……};……};template<typename IterT>//IterT的iterator_category就是用来表现IterT说自己是什么struct iterator_traits{//typedef typename的使用。见**条款**42typedef typename IterT::iterator_category iterator_category;……};

这样对用户自己定义类型行得通,可是对指针行不通,指针也是迭代器。可是指针不能嵌套typedef。以下就是iterator_traits的第2部分了。专门用来支持指针。

为了支持指针迭代器。iterator_traits特别针对类型提供一个偏特化版本号(partial template specialization)。

    template<typename IterT>struct iterator_traits<IterT*>//针对内置指针{typedef random_access iterator_tag iterator_category;……};

如今能够直到实现一个traits class步骤了

  • 确认若干我们希望将来可取得的类型相关信息。

    对于迭代器来首。就是能够取得其分类。

  • 为该信息选择一个名称。对于迭代器是iterator_category。
  • 提供一个template和一组特化版本号。内含你希望支持的类型和相关信息。

如今能够实现一下advance了

    template<typename IterT, typename DistT>void advance(IterT& iter,DisT d){if(typeid(typename std::iterator_traits<IterT>::iterator_category)==typeid(std::random_access_iterator_tag))……}

尽管逻辑是正确,但并不是是我们想要的。抛开编译问题(**条款**48再说),另一个更根本的问题:IterT类型在编译期间获知。所以iterator_traits::iterator_category在编译期间确定。

可是if语句却是在执行期间核定。能够在编译期间完毕的事情推到执行期间,这不仅浪费时间,还造成执行文件膨胀。

要在编译期间确定。能够使用重载。

重载是在编译期间确定的,编译器会找到最匹配的函数来调用

    template<typename IterT, typename DisT>void doAdvance(IterT& iter, Dist d, std::random_access_iterator_tag){iter+=d;}template<typename IterT, typename DisT>void doAdvance(IterT& iter, Dist d, std::bidirectional_iterator_tag){if(d>=0)while(d--) ++iter;else while(d++) --iter;}template<typename IterT, typename DisT>void doAdvance(IterT& iter, Dist d, std::input_iterator_tag){if(d<0)throw std::out_of_range("Negative distance");while(d++) --iter;}template<typename IterT,typename DistT>void advance(IterT& iter,DistT d){doAdvance(iter,d,typename::std::iterator_traits<IterT>::iterator_category();}

由于forward_iterator_tag继承自input_iterator_tag,所以input_iterator_tag版本号的函数能够处理forward迭代器。这是由于public继承是is-a关系。

如今来总结一下怎样使用traits class

  • 建立一组重载函数或函数模板(比如doAdvance)。彼此间差异仅仅在于各自的traits參数。每个函数实现与之接受的traits信息像匹配。
  • 建立一个控制函数或函数模板(比如advance),调用上面的函数并传递traits class信息。

Traits广泛应用在STL。除了上面所说的iterator_traits,还有char_traits用来保存字符类型相关信息。numeric_limits用来保存数值类型相关信息。

TR1(**条款**54导入很多新的traits classes用来提供类型信息。比如is_fundamental推断T是否是内置类型,is_array推断T是否为数组。is_base_of

版权声明:本文博主原创文章,博客,未经同意不得转载。

转载于:https://www.cnblogs.com/hrhguanli/p/4840657.html

《Effective C++》:条款46-条款47相关推荐

  1. Effective C++笔记_条款31将文件间的编译依存关系降至最低

    Effective C++笔记_条款31将文件间的编译依存关系降至最低 这个章节,读了两遍还是不是很清楚,有一种没法和作者沟通的感觉,看来我还是一个C++的初学者呀.好吧,不多说了,回归主题,今天的笔 ...

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

    条款24讨论过为什么唯有non-member函数才有能力"在所有实参身上实施隐式类型转换",该条款并以Rational class的operator*函数为例.我强烈建议你继续看下 ...

  3. Effective c++学习笔记条款20:宁以 pass-by-reference-to-const替换pass-by-value

    Prefer pass-by-reference-to-const to pass-by-value         这个问题在在C++是非常常见的.传值和传引用巨大的差别在于你所使用的参数是其本身还 ...

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

    看看下面这个例子: 1 template<typename T> 2 class Rational{ 3 public: 4 Rational(const T & numerato ...

  5. 【effective c++笔记】条款01 :视c++为一个语言联邦

    c++的4个组成部分: (1)c:c++以c为基础. (2)面向对象设计的c++:包括classes(构造函数和析构函数),封装,继承,多态,virtual函数.... (3)Template C++ ...

  6. Effective C++ 学习笔记 条款05 了解C++默默编写并调用了哪些函数

    当写下一个空类时,编译器会为你合成一个拷贝构造函数.一个拷贝赋值运算符.一个析构函数,如没有声明其他的构造函数,编译器会合成一个默认构造函数.这些都是inline的public成员. 当类有一个引用成 ...

  7. 46. 全排列 47. 全排列 II

    46. 全排列 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 .你可以 按任意顺序 返回答案. 示例 1: 输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2 ...

  8. 46. 全排列/47. 全排列 II

    2020-07-25 1.题目描述 给定一个 没有重复 数字的序列,返回其所有可能的全排列. 2.题解 交换元素 3.代码 class Solution {public:vector<vecto ...

  9. JZ45,46,47,48

    JZ45 输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个. #include <sstream> class Solution { publi ...

最新文章

  1. 微服务架构如何保障双11狂欢下的99.99%高可用
  2. 一文整理深度学习【深度学习win10的Docker配置】
  3. ASP.NET自定义控件开发系列(二)
  4. matlab复杂噪声产生实验报告,matlab加入噪声 - 范文中心
  5. oracle audit文件,oracle 参数文件audit_trail的认识
  6. 《程序设计技术》第五章例程
  7. 《图谋职场——最经济的图形沟通》 一种能提高职场竞争力的沟通能力
  8. Win7 下安装ubuntu14.04双系统
  9. 分析一个在高并发下的财务支付锁的问题
  10. echart横坐标_echart横坐标显示问题
  11. 4r照片尺寸是多大_4寸照片尺寸多少厘米 多少像素
  12. 容错性设计原则(一)
  13. php重置按钮,input 标签中 reset 重置按钮点击后表单不能清空的原因
  14. 组合数(卢卡斯定理)
  15. Git 基础知识 - 查看提交历史记录
  16. 定时网页截图php,浏览器实现网页定时自动截图
  17. 软考学习:吐血整理——自学软考的终极干货
  18. 哈佛架构、冯诺依曼架构、指令集
  19. 花呗能不能不还?支付宝说春节集五福中彩蛋可帮还
  20. jquery点击图片进行放大缩小

热门文章

  1. SELinux系列(十二)安全上下文的修改和设置(chcon和restorecon命令)
  2. 用 Python 爬取了《雪中悍刀行》数据,终于知道它为什么这么火了
  3. 一个500强公司的数据化运营管理实践
  4. 帆软报表-通过代码来创建一个模板文件
  5. java中mouselistener的用法_关于MouseListener接口的简单使用
  6. dbf如何导入oracle_克服Oracle导数一切难题
  7. python中else在循环中的使用(一分钟读懂)
  8. php mysql上机题_PHP+mysql真题
  9. KMP算法总结+Next数组+Nextval数组
  10. Chess DP 思维题