Effective C++笔记—模板与泛型编程
Posted NearXDU
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Effective C++笔记—模板与泛型编程相关的知识,希望对你有一定的参考价值。
模板内容很丰富,分两次记录吧
条款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 List
public:
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
分别表示计算
5×5
和
10×10
的方阵的逆矩阵。
这样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);//ok
pt1->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++笔记—模板与泛型编程的主要内容,如果未能解决你的问题,请参考以下文章