条款四十一:了解隐式接口的和编译期多态

和面向对象编程的显示接口和运行期多态不同,
泛型编程更多是隐式接口和编译期多态。

#include <iostream>
using namespace std;
class A
{public:A() { x = 12; cout << "Create A" << endl; }virtual std::size_t size() const { return x; }virtual void Init() { x = 10; cout << "Init A" << endl; }void swap(A& other) { int t = x; x = other.x; other.x = t; cout << "Swap" << endl; }bool operator != (const int & x){cout << "!=" << endl;return this->x != x;}
private:int x;
};
template<class T>
void Func(T& a)
{if (a.size() > 10 && a != 5){T temp(a);temp.Init();temp.swap(a);}
}
int main()
{A a;Func(a);return 0;
}
/*
Create A
!=
Init A
Swap
*/

1.以不同的参数具现化会导致调用不同的函数,这便是所谓的编译期多态。
2.泛型编程必须支持对应的隐式转换,如果函数中使用到诸如,size(),Init(),swap(),!=

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

条款四十二:了解typename的双重意义

template<class T>
template<typename T>

没什么不同,但是c++并不总是把class和typename视为等价。

涉及嵌套从属名称时只能使用关键字typename:

(从属名称(dependent name):template内出现的名称(示例中的iter)如果相依于某个template参数(示例中的C)的名称。如果从属名称在class内呈嵌套状,则称为嵌套从属名称(nested dependent name)。const_iterator也是类。

#include <iostream>
using namespace std;
class A
{public:typedef int const_iterator;
private:int x;
};
template<typename T>
void Func(T& x)
{T::const_iterator* x;
}
int main()
{A a;Func(a);return 0;
}

这段代码编译会有报错

为什么?
因为我们不知道T::const_iterator是不是一个类型。我们无法确定!
可能T的某个静态成员变量恰好叫const_iterator,或者x碰巧是个global变量名称呢?
那样的话上述代码甚至不再是声明一个local变量而是一个相动作!!!
(T::const_iterator)* (x)!!!
所以c++规定必须使用typename为前缀。

#include <iostream>
using namespace std;
class A
{public:typedef int const_iterator;
private:int x;
};
template<typename T>
void Func(T& a)
{typename T::const_iterator* x;
}
int main()
{A a;Func(a);return 0;
}

任何时候当你想要在template中指涉一个从属类型名称,就必须在紧临它的前一个位置放上关键字typename。
但是有例外!typename不可以出现在base classes list内的嵌套从属类型名称之前,也不可在member initialization list(成员初值列)中作为base class 修饰符。

#include <iostream>
using namespace std;
template<typename T>
class Base
{public:class Nested{public:Nested(int x) { cout << x << endl; }};
};template<typename T>
class Dierived :public Base<T>::Nested
{public:Dierived(int x) :Base<T>::Nested(x) {}int x;
};
int main()
{Dierived<int> d(10);return 0;
}
/*
输出:10
*/

即不能写成
typename Base::Nested

对于这种长名称我们可以通过typedef简写,这是非常常用的技巧,谁不想少写点单词呢?

template<typename T>
void Func(T& a)
{typedef typename T::const_iterator* iter;iter x;
}

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

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

看这一段代码。Send(info)无法通过编译

#include <iostream>
using namespace std;
class CompanyA
{public:void Send(string msg){cout << "A Send : " << msg << endl;}
};
class CompanyB
{ void Send(string msg){cout << "B Send : " << msg << endl;}
};
class MsgInfo
{public:string msg;
};
template<typename Company>
class MsgSender
{void Send(const MsgInfo& info){string msg;msg = info.msg;Company c;c.Send(msg);}
};
template<typename Company>
class LoggingMsgSender :public MsgSender<Company>
{public:void SendLog(const MsgInfo& info){//logSend(info);//编译器不知道Company是什么,所以无法知道MsgSender<Company>是什么}
};
int main()
{return 0;
}

问题在于,当编译器遭遇class template LoggingMsgSender定义式时,并不知道他继承的是什么样的class。主要是不知道Company是什么。更明确的说是无法确定有Send这个函数。

这涉及到模板特化的问题!
我们看一个例子,假设我们引入了一个模板全特化的MsgSender

class CompanyZ
{};
template<>
//一个全特化的MsgSender
class MsgSender<CompanyZ>
//,它和一般template相同,差别只是没有Send
{};

所谓的模板全特化:template MsgSender针对类型Companyz特化了,并且其特化是全面的,也就是说一旦类型参数被定义为Companyz,再没有其他template参数可供变化。
(说人话:模板参数列表中all的模板参数都用具体的类型标识)
当类模板/函数模板进行全特化之后,这个全特化后的类/函数就不是一个模板类/函数了
(因为全特化完成后,模板参数类型为空了,即template<>了,就是一个具体的类/函数)

那么我们再考虑之前的LoggingMsgSender

template<typename Company>
class LoggingMsgSender :public MsgSender<Company>
{public:void SendLog(const MsgInfo& info){//logSend(info);//如果Company==Companyz这个函数不存在}
};

这就是为什么C++拒绝调用的原因:它知道base class template可能被特化,而那个特化版本可能不提供和一般性template相同的接口。

解决方案有三种:
1.加上“this->”

template<typename Company>
class LoggingMsgSender :public MsgSender<Company>
{public:void SendLog(const MsgInfo& info){//logthis->Send(info);}
};

2.使用using声明式

template<typename Company>
class LoggingMsgSender :public MsgSender<Company>
{public:using MsgSender<Company>::Send;void SendLog(const MsgInfo& info){//logSend(info);}
};

3.明白指出被调用的函数位于base class内:

template<typename Company>
class LoggingMsgSender :public MsgSender<Company>
{public:void SendLog(const MsgInfo& info){//logMsgSender<Company>::Send(info);}
};

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

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

使用template可能导致代码膨胀,二进制码会带着重复(或者几乎重复)的代码、数据,或两者。其结果有可能源码看起来合身而整齐,但目标码却不是这么回事。所以该条例用于我们解决template带来的代码膨胀问题。

参考这一篇

#include <iostream>
using namespace std;
template <class T>
class SquareMatrixBase
{public:SquareMatrixBase(T* p) : DataPointer(p) {}void Invert(size_t n) {}//求逆矩阵
private:T* DataPointer;
};
template <class T, size_t n>
class SquareMatrix:private SquareMatrixBase<T> //n*n的矩阵T类型的
{public:SquareMatrix() : SquareMatrixBase<T>(Data){}void Invert(){SquareMatrixBase<T>::Invert(n);//调父类函数}
private:T Data[n * n];
};int main()
{SquareMatrix<int, 10> a;SquareMatrix<int, 5> b;
}

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

条款四十五:运用成员函数模板接受所有兼容类型

真实的指针考研支持隐式转换。但我们自己简单写的指针不能这样

#include <iostream>
using namespace std;
template <class T>
class SmartPtr
{public:SmartPtr(T* p) : p(p) {}
private:T* p;
};class A
{};
class B :public A
{};
int main()
{SmartPtr<A> p1=SmartPtr<B>(new B);//不行shared_ptr<A> p2 = shared_ptr<B>(new B);//可以
}

为什么?如果以带有base-derived关系的A和B类分别具现化某个template,产生出来的两个具现化并不带有base-derived的关系,所以编译器认为这是两个完全无关的类。
解决方案是生成一个member template copy构造函数

#include <iostream>
using namespace std;
template <class T>
class SmartPtr
{public:template<typename U>SmartPtr(const SmartPtr<U> & other):p(static_cast<T*>(other.get()))  {}SmartPtr(T* p) : p(p) {}T* get() const{return p;}
private:T* p;
};class A
{};
class B :public A
{};
int main()
{SmartPtr<A> p1=SmartPtr<B>(new B);//不行shared_ptr<A> p2 = shared_ptr<B>(new B);//可以
}

请记住:
请使用member function templates(成员函数模板)生成“可接受所有兼容类型”的函数;
如果你声明member templates用于“泛化copy构造”或“泛化assignment操作”,你还是需要声明正常的copy构造函数和copy assignment操作符。

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

为什么唯有non-menber函数才有能力”在所有实参身上实施隐式类型转换”。

#include <iostream>
using namespace std;
template <typename T>
class A {public:A(T t) {this->t = t;}A(const A<T>& e) {this->t = e.t;}const A<T>& operator=(const A<T>& e) {this->t = e.t;return *this;}template<typename T>const A<T> operator*(const A<T>& t1, const A<T>& t2) {return A<T>(t1.t*t2.t);}
private:T t;
};
int main() {A<int> t1(10);A<int> t2 = t1 * 10;//报错return 0;
}

上述失败的原因是编译器不知道我们想要调用哪个函数,它们试图想出什么函数名为operator*的template具现化。但它们必须要知道T是什么,它们没有这个能耐。

第一个参数t1是A类型的,但第二个参数是int类型,那么编译器如何推算出T?
只需要:template class内的friend声明式可以指涉某个特定函数。那意味class A可以声明operator*为friend函数。所以编译器总是能够在class A具现化时得知T。

#include <iostream>
using namespace std;
template <typename T>
class A {public:A(T t) {this->t = t;}A(const A<T>& e) {this->t = e.t;}const A<T>& operator=(const A<T>& e) {this->t = e.t;return *this;}friend const A<T> operator*(const A<T>& t1, const A<T>& t2) {return A<T>(t1.t*t2.t);}
private:T t;
};
int main() {A<int> t1(10);A<int> t2 = t1 * 10;//没报错return 0;
}

请记住:
当我们编写一个class temlate,而它所提供之“与此template相关的”函数支持”所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”

条款四十七:请使用traits classes 表现类型信息

完全看不懂。
主要是搞不明白traits。大概好像是利用模板偏特化使得模板得到类型?
traits
模板与泛型
请记住:
Traits classes使得”类型相关信息”在编译期可用。它们以templates和“templates特化完成实现”。
整合重载技术后,traits classes有可能在编译期对类型执行if。。else测试

条款四十八:认识template元编程

Template metaprogramming(TMP 模板元编程)
它有两个伟大效力:
第一,它让某些事情更容易。
第二,由于TMP执行于C++编译期,因此可将工作从运行期转移到编译期。这导致的一个结果是,某些错误原本通常在运行期才能侦测到,现在可在编译期找出来。另一个结果是,使用TMP的C++程序可能在每一方面都更高效:较小的可执行文件、较短的运行期、较少的内存需求。然而将工作从运行期转移到编译期的另一个结果是,编译时间变长了。

好的,我们来写hello world==》阶乘

#include <iostream>
using namespace std;
template<unsigned n>
struct Factorial {enum { value = n * Factorial<n - 1>::value };
};
//特化模板,Factorial<0>的value值为1
template<>
struct Factorial<0> {enum { value = 1 };
};
int main() {Factorial<5> a;cout << a.value << endl;return 0;
}

有了这个TMP,只要你指涉Factorial::value就可以得到n阶乘值。循环发生在template具现体内部指涉另一个template具现体Factional之时。我们需要一个特殊情况造成递归结束,这里的特殊情况是template特体化Factorial<0>。

此外,为了领悟TMP之所以值得学习,很重要的一点是先对它能够达成什么目标有一个比较好的理解,下面是三个例子:
第一、确保度量单位正确。如果使用TMP,就可以确保(在编译器)程序中所有量度单位的组合都正确,不论其计算多么复杂。这也是为什么TMP可被用来进行早期错误侦测。
第二,可以优化矩阵运算。如果使用高级、与TMP相关的template技术,即所谓的expression templates,就有可能消除多个矩阵相乘时产生的临时对象并合并循环。
第三,可以生成客户定制的设计模式实现品。运用所谓policy-based design的TMP-based技术,有可能产生一些templates用来表述独立的设计选项,然后可以任意结合他们,导致模式实现品带着客户定制的行为。此外也可以用来避免生成对某些特殊类型并不合适的代码。

请记住:
TMP可将工作由运行期转移到编译期,因而得以实现早期错误侦测或者更高的执行效率。
TMP可被用来生成“基于政策选择组合”的客户定制代码,也可以用来避免生成对某些特殊类型并不适合的代码。(这句话看不懂也没关系)

《Effective C++》 总结篇(模板与泛型编程)相关推荐

  1. Effective C++ 学习笔记 第七章:模板与泛型编程

    第一章见 Effective C++ 学习笔记 第一章:让自己习惯 C++ 第二章见 Effective C++ 学习笔记 第二章:构造.析构.赋值运算 第三章见 Effective C++ 学习笔记 ...

  2. c++ swap函数头文件_C++函数模板(泛型编程)

    模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码. 模板是创建泛型类或函数的蓝图或公式.库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念. 每个容器都有一个单 ...

  3. C++ Primer 学习笔记_75_模板与泛型编程 --模板定义

    模板与泛型编程 --模板定义 引言: 所谓泛型程序就是以独立于不论什么特定类型的方式编写代码.使用泛型程序时,我们须要提供详细程序实例所操作的类型或值. 模板是泛型编程的基础.使用模板时能够无须了解模 ...

  4. c++ 函数模板_C++函数模板(泛型编程)

    模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码. 模板是创建泛型类或函数的蓝图或公式.库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念. 每个容器都有一个单 ...

  5. html5新年网页做给父母的,给父母的感谢信5篇模板

    谁言寸草心,报得三春晖.你知道给父母的感谢信该怎么写吗?下面是小编为大家带来的给父母的感谢信,欢迎大家阅读和参考!希望能给你带来帮助! 给父母的感谢信1 亲爱的爸爸妈妈: 很快就是母亲节了,对,母亲节 ...

  6. C++ 模板与泛型编程简述

    目录 1.什么是模板和泛型编程 2.定义及使用模板 1.什么是模板和泛型编程 什么是模板?什么是泛型编程?模板的概念与泛型编程是相辅相成的.想象一个场景:我们需要比较两个整数或两个字符串的大小,假如你 ...

  7. (十六)模板与泛型编程

    (十六)模板与泛型编程 1定义模板 1.1函数模板 1.2类模板 1.2.1定义 1.2.2实例化 1.2.3类模板与友元 1.2.4类模板类型别名 1.2.5类模板静态函数 1.3模板参数 1.4成 ...

  8. Effective C++ --7 模板与泛型编程

    上一篇Effective C++ -- 6 继承与面向对象设计 41.了解隐式接口和编译器多态 (1)class和template都支持接口和多态.Class支持显示接口,多态是指virtual引起的 ...

  9. 【effective c++读书笔记】【第7章】模板和泛型编程(3)

    条款46:需要类型转换时请为模板定义非成员函数 对条款24的例子进行模板化: #include<iostream> using namespace std;template<type ...

最新文章

  1. Zookeeper的目录结构
  2. 2019标杆案例复盘(下):沟通无界——生活社交篇
  3. HTML页面仿iphone数字角标
  4. 事务里面捕获异常_spring 事务回滚
  5. 工厂参观记:.NET Core 中 HttpClientFactory 如何解决 HttpClient 臭名昭著的问题
  6. java浪漫代码_Elasticsearch,从一个浪漫的故事开始(原理篇)
  7. java入门预备知识一
  8. Android xml文件的序列化
  9. Maven中如何配置WAR依赖WAR和JAR的多模块项目结构
  10. Mac删除Windows10后空间丢失解决
  11. Vue.js学习系列(八)---使用路由搭建单页应用(一)
  12. 简单总结下8.25技术大会感受
  13. 解决com.lowagie.text.DocumentException: Font 'STSong-Light' with 'UniGB-UCS2-H' is not recognized.
  14. 常见后端数据存储问题解决方案
  15. 程序员的“九阳神功”——设计模式
  16. TruckSim Quick Start Guide(TruckSim快速入门)
  17. Camera2 YUV_420_888转NV21
  18. 苹果胜三星震惊国产手机
  19. Spark综合项目:企业电商分析平台
  20. 网易云音乐怎样下载mp3格式的音乐

热门文章

  1. UI动效设计从入门到项目实战
  2. 一分钟教你如何在没有网络的电脑上安装VsCode插件
  3. 无法完成请求因为找到不知名或无效的jpeg
  4. ai外呼营销系统_上海AI外呼系统
  5. 如何查看python源代码_查看“使用python制造渗流模型”的源代码
  6. 计算机学硕需要发表论文才能毕业吗,计算机相关专业的研究生,马上快要毕业了,还写不出毕业论文......
  7. wireshark抓的包中文显示点点....
  8. ipad能运行python_ipad上可以运行python吗?
  9. 三星s4开机显示无服务器,三星S4显示无服务选定网络(CHN-UNICON)不可用
  10. 一幅长文细学React(一)——入门