本文主要是介绍Effective C++ 7.0 模板与泛型编程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
条款41 了解隐式接口和编译器多态1. classes 和 templates 都支持 接口 和多态
2. 对 classes 而言接口是 显式 的,以函数签名为中心, 多态则是通过 virtual 函数发生于运行期间。
3. 对 template 参数而言, 接口是隐式的, 奠基于有效的表达式。
多态则是通过 template具现化 和 函数重载解析 发生于 编译期。
条款42 了解typename的双重意义
1. 声明 template 参数时, 前缀关键字 class 和 typename 可互换。
2. 请使用关键字typename标识嵌套从属类型名称; 但不得在 base class list(基类列) 或 member initialization list(成员初值列)内以它作为base class 修饰类。
template<typename C>
void f(const C& container, // 不允许使用 typename
typename C::iterator iter // 一定要使用 typename 说明C::iterator 是一种数据类型
);
template<typename T>
class Derived: public Base<T>::Nested // base class list 中 不允许 typename
{
public:
explicit Derived(int x):Base<T>::Nested(x) // member initialization list 中 不允许 typename
{
// 嵌套从属类型名称 既不在base class list 也不在 mem init list 中
// 作为一个base class 修饰符 需要加上 typename
typename Base<T>::Nested temp;
}
};
条款43: 学习处理模板化基类内的名称
1. 可在 derived class templates 内通过 "this->" 指涉 base class templates 内的成员名称, 或借由一个明白写出
的"base class 资格修饰符"完成。
采用template的一个算法:
class CompanyA
{
public:
... ...
void sendCleartext(const std::string& msg);
void sendEncrypted(const std::string& msg);
... ...
};
class CompanyB
{
public:
... ...
void sendCleartext(const std::string& msg);
void sendEncrypted(const std::string& msg);
... ...
};
... ...
class MsgInfo { ... };
template<typename Company>
class MsgSender
{
public:
...
void sendClear(const MsgInfo& info)
{
std::string msg;
Company c;
c.sendCleartext(msg);
}
// 类似sendClear, 唯一不同的是 这里调用了 c.sendEncrypted
void sendSecret(const MsgInfo& info)
{ ... }
};
// 日志记录某些信息
template<typename Company>
class LoggingMsgSender:public MsgSender<Company>
{
public:
... ...
void sendClearMsg(const MsgInfo& info)
{
sendClear(info); // 调用base class函数,这段代码无法通过编译。
}
... ...
};
验证:
当
class CompanyZ
{
public:
... ...
// 这个class 不提供 sendCleartext函数 void sendCleartext(const std::string& msg);
void sendEncrypted(const std::string& msg);
... ...
};
template<> // 全特化
class MsgSender<CompanyZ>
{
public:
...
// 类似sendClear, 唯一不同的是 这里调用了 c.sendEncrypted
void sendSecret(const MsgInfo& info)
{ ... }
};
此时若在
// 日志记录某些信息
template<typename Company>
class LoggingMsgSender:public MsgSender<Company>
{
public:
... ...
void sendClearMsg(const MsgInfo& info)
{
sendClear(info); // 如果Company == CompanyZ 这个函数不存在
// 所以上面代码编译不通过是有道理的。
}
... ...
};
为了不让其编译失效,有三种办法:
(1) 加上 this->
template<typename Company>
class LoggingMsgSender:public MsgSender<Company>
{
public:
... ...
void sendClearMsg(const MsgInfo& info)
{
this->sendClear(info); // 成立,假设sendClear将被继承
}
... ...
};
(2)使用using声明式
template<typename Company>
class LoggingMsgSender:public MsgSender<Company>
{
public:
using MsgSender<Company>::sendClear; // 告诉编译器,请他假设sendClear位于base class 内。
... ...
void sendClearMsg(const MsgInfo& info)
{
sendClear(info); // 成立,假设sendClear将被继承
}
... ...
};
(3)明白指出被调用位于base class 内:
template<typename Company>
class LoggingMsgSender:public MsgSender<Company>
{
public:
... ...
void sendClearMsg(const MsgInfo& info)
{
MsgSender<Company>::sendClear(info); // 成立,假设sendClear将被继承
}
... ...
};
条款44:将与参数无关的代码抽离 templates
1. Templates生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依联系
因非类型模板参数而造成的代码膨胀,往往可以消除,做法是以函数参数或class成员变量替换template参数。
举例:
template<typename T,std::size_t n>
class SquareMatrix
{
public:
... ...
void invert(); // 求逆矩阵
};
现在考虑这些代码:
SquareMatrix<double,5> sm1;
sm1.invert(); // 调用 SquareMatrix<double,5>::invert();
SquareMatrix<double,10> sm2;
sm2.invert(); // 调用 SquareMatrix<double,10>::invert();
这里具现了两份,引起了代码膨胀。
避免代码膨胀
方法1:
template<typename T> // 与尺寸无关的 base class
class SquareMatrixBase
{
protected:
... ...
void invert(std::size_t matrixSize);
... ...
};
template<typename T,std::size_t n>
class SquareMatrix: private SquareMatrixBase<T>
{
private:
using SquareMatrixBase<T>::invert;
public:
... ...
void invert() { this->invert(n); } // 制造一个inline调用 调用base class版的invert.
}
SquareMatrix<double,5> sm1;
sm1.invert();
SquareMatrix<double,10> sm2;
sm2.invert();
// 由于共享同一份 SquareMatrixBase<double> 则只具现一份代码
方法2:
存储一份指针:
template<typename T>
class SquareMatrixBase
{
protected:
SquareMatrixBase(std::size_t n, T* pMem): size(n), pData(pMem) { }
void setDataPtr(T* ptr) { pData = ptr; }
... ...
private:
std::size_t size; // 矩阵的大小
T* pData; // 指针, 指向矩阵的内容
};
// 这允许 derived class 决定内存分配方式。
某些将矩阵存储在内部
template<typename T,std::size_t n>
class SquareMatrix: private SquareMatrixBase<T>
{
public:
SquareMatrix():SquareMatrixBase<T>(n,data) {}
...
private:
T data[n*n];
};
// 将每一个矩阵的数据放进heap
template<typename T,std::size_t n>
class SquareMatrix: private SquareMatrixBase<T>
{
public:
SquareMatrix():SquareMatrixBase<T>(n,0), pData(new T[n*n])
{
this->setDataPtr(pData.get());
}
...
private:
boost::scoped_array<T> pData;
};
条款45: 运用成员函数模板接受所有兼容类型
原始指针类型之间的转换是隐式转换,因此并未声明为explicit。
template<typename T>
class SmartPtr
{
public:
template<typename U>//member template,生成copy构造函数
SmartPtr( const SmartPtr<U>& other): heldPtr(other.get()) { } //暗示只有U*可转为T*才可通过编译
T* get( )const { return heldPtr; }
private:
T* heldPtr;
};
在class内声明泛化copy构造函数并不会阻止编译器生成它们自己的copy构造函数(non-template),
所以如果想要控制copy构造函数的方方面面必须同时声明泛化copy构造函数和正常的copy构造函数。
同理,适用于赋值操作。
template<class T>
class shared_ptr
{
public:
shared_ptr ( shared_ptr const& r);
template<class Y>
shared_ptr( shared_ptr<Y> const& r);
shared_ptr& operator = (shared_ptr const& r);
template<class Y>
shared_ptr& operator = (shared_ptr<Y> const& r);
};
1. 请使用 member function templates(成员函数模板) 生成 "可接受所有的兼容类型"的函数
2. 如果你声明 member templates 用于 "泛华copy构造函数" 或 "泛化assignment操作",
你还是需要声明正常的 copy构造函数 和 copy assignment 操作符。
条款46: 需要类型转换时请为模板定义 非成员函数
template<typename T>
class Rational
{
public:
Rational(const T& numerator = 0, const T& denominator = 1):n(numerator),d(denominator){}
const T numerator() const{return n;}
const T denominaotr() const{return d;}
private:
T n, d;
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs)
{
return Rational<T>(lhs.numerator()*rhs.numerator(), lhs.denominaotr()*rhs.denominaotr());
}
Rational<int> oneHalf(1,2);
Rational<int> result = oneHalf * 2; //编译error
operator*第一参数被声明为Rational<T>,而传给operator*的第一实参的类型是Rational<int>,所以T是int。第二参数是Rational<T>,但传入的实参是整数2。
template实参推导过程中并不考虑通过构造函数而发生的隐式类型转换。因此,不会转换为Rational<int>。
解决方法:
template class内的friend声明式可以指涉某个特定函数。因此可以声明operator*是Rational<T>的一个friend函数。
编译器总是能够在class Rational<T> 具现化时得知T。
template<typename T>
class Rational
{ …
friend const Rational<T> operator*(const Rational& lhs, const Rational& rhs); // 模板函数必须放在 .h 文件中 只能声明 即只能放在类里面。
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs){ … }
可以通过编译,但不能链接。
编译器知道我们要调用哪个函数,但那个函数只被声明于Rational内,并没有被定义出来。
尽管我们意图令此class外部的operator* template提供定义式,但是行不通。
最简单的方法是:
template<typename T>
class Rational
{ …
friend const Rational<T> operator*(const Rational& lhs, const Rational& rhs)
{
return Rational<T>(lhs.numerator()*rhs.numerator(), lhs.denominaotr()*rhs.denominaotr());
}
};
为了让类型转换可能发生于所有实参身上,我们需要一个 non-member 函数; 为了让这个函数自动具现化,
我们需要将它声明在class内部。而满足两项的只有一个办法:在class内部声明一个friend函数。
当我们编写一个class template, 而它所提供之“与此template相关的”函数支持"所有函数之隐式类型转换"时,
请将那些函数定义为"class template 内部的 friend函数".
条款47: 请使用 traits classes 表现类型信息
参考<STL源码剖析>内的实现
条款48: 认识template元编程
TMP是编写template-based C++程序并执行编译期的过程。TMP是以C++写成,执行于C++编译器内的程序。一旦TMP程序结束执行,其输出也就是templates具现出来的若干C++源码,便会一如往常地被编译。
TMP(template metaprogramming)模板元编程可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的行为效率。
TMP是图灵完全的,足以计算任何事物。条款47中的traits就是TMP,traits引发编译期发生于类型身上的if … else计算。
示例:编译期计算阶乘!
template<unsigned n>
struct Factorial{
enum {value = n * Factorial<n-1>::value};
};
template<>
struct Fatorial<0>{
enum {value = 1};
};
std::cout<<Factorial<5>::value<<endl; //输出120
这篇关于Effective C++ 7.0 模板与泛型编程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!