Effective C++笔记(9)—模板与泛型编程(一)
来源:互联网 发布:编程思维 编辑:程序博客网 时间:2024/05/29 07:41
模板内容很丰富,分两次记录吧
条款41:了解隐式接口和编译期多态
对应了显示接口和运行期多态。
下面一个例子说明:
void func(Widget &w){ cout<<w.size()<<endl;}
func
参数类型被声明为Widget
,所以w必须支持Widget接口。而Widget类中包含的接口可以在声明Widget的文件中(Widget.h)找到,因此被称为显示接口。Widget的接口可能是一个虚函数或者纯虚函数(取决于子类是否继承父类的默认版本),在运行期将根据w的动态类型决定究竟调用哪一个版本。
模板编程引入了编译器多态和隐式接口的概念,看下面例子:
template <class T>void func(T &t){ cout << t.size() << endl;}
该模板函数要求T必须支持size接口,否则在编译器具现化的时候就会报错,比如说:
template <class T>void func(T &t){ cout << t.size() << endl;}class Foo{};int main(){// int a = 5;// func<int>(a);//编译期报错C2228 size的左边需要为class/struct/union类型// Foo foo;// func<Foo>(foo);//编译期报错C2039 size不是Foo的成员 string s = "hello"; func(s);//ok 输出5 system("pause"); return 0;}
上面的例子可以看出,传入模板实参需要支持模板函数中所支持的隐式接口,否则将在编译期报错。上述例子中除了string
,还可以是vector
等STL容器包含size接口,在编译器具现化出不同的func版本,便是所谓的编译期多态。
简单的理解:
编译器多态:哪一个重载函数该被调用
运行期多态:哪一个虚函数将被绑定
当然除此之外,隐式接口需要基于有效的表达式,比如上述例子中,如果某个结构体中包含一个接口size(),但返回类型是一个void,那么在编译器也会有问题,因为<<操作符不接受void类型的右操作数(error C2679)
条款42:了解typename的双重意义
在模板声明的时候可以这样:
template <class T> class Widget;template <typename T> class Widget;
使用class和typename都是ok的,但是这里要介绍typename另外一个class无法替代的作用。
举个例子:
template <class C>void print2nd(const C& container){ if (container.size() >= 2) { C::const_iterator iter(container.begin()); ++iter; C::value_type value= *iter; cout << value << endl; }}//打印容器的第二个元素
这里用到了两个嵌套从属类型: C::const_iterator
C::value_type
这里可能有歧义,虽然我们知道在STL容器中进行了typedef。但C::const_iterator
或者C::value_type
可能是类型C里面的static成员变量,这样就会造成歧义。合法的C++代码应该加上typename
关键字明确这是一个类型名:
template <class C>void print2nd(const C& container){ if (container.size() >= 2) { typename C::const_iterator iter(container.begin()); ++iter; typename C::value_type value= *iter; cout << value << endl; }}
在嵌套从属类型名前需要加上typename,除了以下情况:
template<class T>class Derived :public Base<T>::Nested{//1.不允许出现在Base Class Listpublic: explicit Derived(int x): Base<T>::Nested(x){}//2.不允许出现在成员初始化列表中。 { typename Base<T>::Nested tmp;//非上述两种情况需要加typename }};
另外书中给出了最后一个例子,这个例子看过STL源码剖析的应该不陌生:
template <typename iterT>void func(iterT iter){ typename std::iterator_traits<iterT>::value_type t(*iter);}
这里用迭代器萃取器获得迭代器所指对象的类型,做为临时变量t
的类型。
结合书中的两个例子,上述print2nd也可以这样写:
template <class C>void print2nd(const C& container){ if (container.size() >= 2) { typename C::const_iterator iter(container.begin()); ++iter; typedef typename C::const_iterator const_iterator; typedef typename std::iterator_traits<const_iterator>::value_type value_type; value_type value(*iter); cout << value << endl; }}
条款43:学习处理模板化基类的名称
先来看书上的例子:
class CompanyA{public: void sendCleartext(const string&msg) { cout << "A::sendCleartext" << endl << msg << endl; } void sendEncrypted(const string&msg) { cout << "A::sendEncrypted" << endl << msg << endl; }};class CompanyB{public: void sendCleartext(const string&msg) { cout << "B::sendCleartext" << endl << msg << endl; } void sendEncrypted(const string&msg) { cout << "B::sendEncrypted" << endl << msg << endl; }};template <class Company>class MsgSender{public: void sendClear(const string &msg) { Company c; c.sendCleartext(msg); }};int main(){ //CompanyA a; MsgSender<CompanyA> sa; sa.sendClear("zhangxiao");//调用A的版本 MsgSender<CompanyB> sb; sb.sendClear("zhangxiao2");//调用B的版本 system("pause"); return 0;}
这是template的解法,当然完全可以用一个Company接口类,然后A和B分别继承这个抽象类,然后在实现虚方法。
不过本条款要说的核心是,模板基类的问题:
class CompanyZ{public: void sendEncrypted(const string&msg) { cout << "Z::sendEncrypted" << endl << msg << endl; }};template <>class MsgSender < CompanyZ >{public: void sendClear(const string &msg) { CompanyZ cz; cz.sendCleartext(msg); }};template <class Company>class LoggingMsgSender :public MsgSender<Company>{public: void sendClearMsg(const string &msg) { //在vs2013并没有出现作者说的编译错误问题!! sendClear(msg); }};int main(){ LoggingMsgSender<CompanyA> lms; lms.sendClearMsg("zhangxiao"); system("pause"); return 0;}
在之前的基础上,加入了一个CompanyZ的特化版本,并用一个派生类继承了MsgSender。
在vs2013并没有出现作者说的编译错误问题!!
不过可能这是编译器相关,我并没有在gcc下尝试编译,不过作者用了一个词语:对严守规律的编译器而言,无法通过
作者在书中提到,在LoggingMsgSender具现化之前,并不能知道它继承的东西是何物,也就是不知道class MsgSender<Company>
看起来像什么,就更不用说知道有个sendClear
函数了
更加严谨的做法有三种:
//1.template <class Company>class LoggingMsgSender :public MsgSender<Company>{public: void sendClearMsg(const string &msg) { this->sendClear(msg); }};//2.template <class Company>class LoggingMsgSender :public MsgSender<Company>{public: using MsgSender<Company>::sendClear; void sendClearMsg(const string &msg) { sendClear(msg); }};//3.template <class Company>class LoggingMsgSender :public MsgSender<Company>{public: void sendClearMsg(const string &msg) { MsgSender<Company>::sendClear(msg); }};
条款44:将参数无关的代码抽离template
两个函数都要调用相同的代码,我们会把相同的的代码放到一个函数中。
两个类中的某些代码重复,你会把共同的部分放到一个新class里面去,然后使用继承、复合等手段重复利用。
编写模板时,我们也希望达到相同的效果,但这并不容易,因为在具现化之前,我们需要预测一下哪些部分是重复的。先看书中的例子:
template <class T,std::size_t n>class SquareMatrix{public: void invert() { cout << "invert" << " " << n << endl; }};int main(){ SquareMatrix<int, 5>m1; SquareMatrix<int, 10>m2; m1.invert();// invert 5 m2.invert();//invert 10 system("pause"); return 0;}
上述代码通过非类型参数
将模板类具象化出两个版本的invert
分别表示计算
这样template就造成了代码膨胀了,对其进行第一次修改:
template <class T>class SquareMatrixBase{protected: void invert(std::size_t n) { cout << "invert" << " " << n << endl; }};template<class T,std::size_t n>class SquareMatrix :private SquareMatrixBase < T >{public: void invert() { SquareMatrixBase<T>::invert(n); }};int main(){ SquareMatrix<int, 5>m1; SquareMatrix<int, 10>m2; m1.invert();// invert 5 m2.invert();//invert 10 system("pause"); return 0;}
这样不同尺寸大小的矩阵只有一个版本的Invert。用一个模板基类去做处理,最后书中给出了这个例子的终极版本,就是解决如何将矩阵数据传给基类:
书中用了boost::scoped_array
这是一个管理动态数组的智能指针,我的VS下面没有装boost,就用shared_ptr/unique_ptr
来代替了(https://stackoverflow.com/questions/8624146/does-c11-have-wrappers-for-dynamically-allocated-arrays-like-boosts-scoped-ar)。
template <class T>class SquareMatrixBase{protected: SquareMatrixBase(size_t n_, T* p) :size(n_), pData(p){}//构造函数 void setDataPtr(T* p){ pData = p; }//set 函数 void invert() { cout << "invert" << " " << size << endl; for (size_t i = 0; i < size*size; ++i) cout << pData[i] << endl; }private: std::size_t size; T* pData;};template<class T,std::size_t n>class SquareMatrix :private SquareMatrixBase < T >{public: SquareMatrix() :SquareMatrixBase<T>(n, 0), //pData(new T[n*n]{}, std::default_delete<T[]>())//shared_ptr需要deleter pData(new T[n*n]{}) { SquareMatrixBase<T>::setDataPtr(pData.get());//传给base } void invert() { SquareMatrixBase<T>::invert(); } void SetData(T t[]) { pData.reset(t); SquareMatrixBase<T>::setDataPtr(pData.get());//传给base }private: //shared_ptr<T>pData;//shared_ptr这里除了需要指定deleter它不重载[] unique_ptr<T[]>pData;};int main(){#if 1 SquareMatrix<int, 3>m1; int *array1 = new int[3 * 3]; for (int i = 0; i < 9; ++i) { array1[i] = i + 1; } m1.SetData(array1); m1.invert();// #endif system("pause"); return 0;}
条款45:运用成员函数模板接受所有兼容类型
智能指针方便我们管理内存,原始指针方便做隐式转换,本条款通过实现智能指针的例子来讲述:
class A{};class B : public A{};template <class T>class SmartPtr{public: explicit SmartPtr(T* t){}};int main(){ A *pa= new B;//ok SmartPtr<A>pt1 = SmartPtr<B>(new B);//error delete pa; system("pause"); return 0;}
我们希望智能指针也能做到类之间的隐式转换,事实上,shared_ptr
已经实现,不过我们就是要学习如何实现:
shared_ptr<A>pt1 = shared_ptr<B>(new B);//okpt1->func();//b::func
这里,我们需要为SmartPtr
的构造函数写一个构造模板。即成员模板(member template)
template <class T>class SmartPtr{public: template <class U> SmartPtr(const SmartPtr<U>&other);};
以上代码的意思是,对任何类型T和任何类型U,这里可以根据SmartPtr生成SmartPtr只要U到T之间存在隐式转换关系。
1.成员函数模板表示泛化的模式,表示生成“可接受所有兼容类型”的函数
2.如果声明了泛化的拷贝构造函数和赋值操作符,那么也要声明正常的拷贝构造函数和赋值运算符
基于上述两点约束,根据书中的例子,实现一个简单的智能指针。
class A{public: virtual void func(){ cout << "a::func" << endl; } virtual ~A(){}};class B : public A{public: virtual void func(){ cout << "b::func" << endl; }};//////////////////////////////////////template <class T>class SmartPtr{public: //返回use_count int *use_count()const { return count; } //返回原始对象的指针 T*get()const { return heldPtr; } //析构函数 ~SmartPtr() { if (--(*count) == 0) { delete count; delete heldPtr; count = nullptr; heldPtr = nullptr; } }// //默认构造函数 SmartPtr() :count(nullptr), heldPtr(nullptr) { } //使用内置指针初始化的构造函数 template <class U> explicit SmartPtr(U * pu) : heldPtr(pu) { count = new int(1); } //泛化拷贝构造函数 template <class U> SmartPtr(SmartPtr<U>&other) :count(other.use_count()),heldPtr(other.get()) { ++(*count); } //拷贝构造函数 SmartPtr(SmartPtr&other) :count(other.use_count()), heldPtr(other.get()) { ++(*count); } //泛化赋值符号重载 template<class U> SmartPtr &operator=(const SmartPtr<U>&other) { ++(*other.use_count()); if ((count != nullptr) && (--(*count) == 0)) { delete heldPtr; delete count; } heldPtr = other.get(); count = other.use_count(); return *this; } //赋值符号重载 SmartPtr &operator=(const SmartPtr&other) { ++(*other.use_count()); if ((count!=nullptr)&&(--(*count) == 0)) { delete heldPtr; delete count; } heldPtr = other.get(); count = other.use_count(); return *this; } //->重载 作用类似于get() T* operator->()const { return heldPtr; }private: int* count; T* heldPtr;};int main(){ A *pa = new B;//ok //B*pb = new B; SmartPtr<A>global; { SmartPtr<B>pb(new B); SmartPtr<A>pt1 = SmartPtr<B>(new B);//copy构造函数 global = pt1;//赋值函数 global = pb; global->func();// SmartPtr<A>pt2 = pt1; pt1->func(); } delete pa; system("pause"); return 0;}
- Effective C++笔记(9)—模板与泛型编程(一)
- Effective C++(七)模板与泛型编程
- Effective C++(七)模板与泛型编程
- Effective C++笔记(10)—模板与泛型编程(二)
- (Effective C++)第七章 模板与泛型编程 (Templates and Generic Programming)
- Effective C++(七)模板与泛型编程
- effective C++: 7模板与泛型编程
- <<Effective C++>>读书笔记7: 模板与泛型编程
- 《Effective C++》第七章:模板与泛型编程
- Effective C++ 读书笔记(七) 模板与泛型编程
- Effective C++ 笔记 第七部分 模板与泛型编程
- C++Primer---模板与泛型编程(一)
- 《Effective C++》笔记(一)
- 《Effective C++》笔记(一)
- C++primer阅读笔记-模板与泛型编程(重载与模板)
- effective c++ -- 模板与泛型编程
- C++primer 阅读笔记-模板与泛型编程(函数模板)
- C++primer 阅读笔记-模板与泛型编程(类模板)
- string类深拷贝,写时拷贝
- windows多网卡使用改进
- 第八章 表格单元格选取以及UIAlertController(一)
- OpenGL的第一天【VS2017+OpenGL环境的配置】
- 多态的技能点(前提条件、向上转型、向下转型)
- Effective C++笔记(9)—模板与泛型编程(一)
- js逻辑运算符
- POJ
- L1-048. 矩阵A乘以B
- HDU-1166敌兵布阵(线段树,树状数组)
- 通过内容提供器获取手机联系人信息
- 聚合工程模块配置需要
- hdu 6035 Colorful Tree
- QTableWidget中实现表格的自动换行