Effective C++ 第七章:模板与泛型编程

第七章:模板与泛型编程

条款41:了解隐式接口和编译期多态

  • 编译期的多态和运行期的多态的区别:

    • 哪一个重载函数该被调用:发生在编译期
    • 哪一个virtual函数该被绑定:发生在运行期
  • 显式接口与隐式接口的区别:

    • 显式接口由函数的签名式(函数名称、参数类型、返回类型)构成。如:
    class Widget{
    public:
    	Widget();
    	virtual ~Widget();
    	virtual std::size_t size() const;
    	virtual void normalize();
    	void swap(Widget& other);
    }
    
    • 隐式接口并不基于函数签名式,而是由有效表达式组成。
    template<typename T>
    void doProcessing(T& w)
    {
    	if(w.size() > 10 && w!=someNastyWidget){
    		...
    	}
    }
    

    T的隐式接口有如下约束:
    1.它必须提供一个名为size的成员函数,该函数返回一个整数值。
    2.它必须支持一个operator!=函数,用来比较两个T对象。这里我们假设someNastyWidget的类型为T

  • 小结:

    • 对于class而言,接口是显式的,以函数签名为中心。多态则是通过virtual 函数发生于运行期。
    • 对template参数而言,接口是隐式的,基于有效表达式。多态则是通过template具现化和函数重载解析,发生于编译期。

条款42:了解typename的双重意义

  • 当我们声明template类型参数,class和typename的意义完全相同。
template<class T> class Widget;
template<typename T> class Widget;
  • 使用关键字typename标识嵌套从属类型名称
//该代码并不能通过编译
template<typename C>
void print2nd(const C& container)
{
	if(container.size() >=2)
	{
		C::const_iterator iter(container.begin());
		++iter;
		int value = *iter;
		std::cout << value;
	}
}
  • 从属名称:template内出现的名称如果相依于某个template参数,称之为从属名称。如C::const_iterator实际是什么必须取决于template C.

  • 如果从属名称在class内呈嵌套状,我们称它为嵌套从属名称。

  • 非从属名称:并不依赖于任何template参数的名称。如:value 的值是int.

  • C++有个规则:如果解析器在template中遭遇一个嵌套从属名称,它便假设这个名称不是个类型,除非你告诉它是(此时便需要使用typename)

//上述代码中修改
typename C::const_iterator iter(container.begin());
  • 自动决定类型
template<typename IterT>
void workWithIterator(IterT iter)
{
	typename std::iterator_traits<IterT>::value_type temp(*iter);
	...
}

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

模板类之间的继承

//基类
template<typename Company>
class MsgSender{
public:
	void sendClear(){...}
	...
};
//子类
template<typename Company>
class Logging:public MsgSender<Company>{
public:
	void sendClaerMsg()
	{
		sendClear();	//调用基类函数:这段代码无法通过编译
	}
	...
}

无法通过编译原因:编译器会认为sendClear不存在。在编译时,无法知道Company是什么,那么便无法确定class MsgSender是什么,所以便无法知道是否有个sendClear()函数。

解决办法:

  1. 在base class 函数调用动作之前加上this->
template<typename Company>
class Logging:public MsgSender<Company>{
public:
	void sendClaerMsg()
	{
		this->sendClear();	//调用基类函数:这段代码无法通过编译
	}
	...
}
  1. 使用using声明式
template<typename Company>
class Logging:public MsgSender<Company>{
public:
	using MsgSender<Company>::sendClear;
	void sendClaerMsg()
	{
		sendClear();	//调用基类函数:这段代码无法通过编译
	}
	...
}
  1. 明白指出被调用的函数位于base class内:
template<typename Company>
class Logging:public MsgSender<Company>{
public:
	void sendClaerMsg()
	{
		MsgSender<Company>::sendClear();	//调用基类函数:这段代码无法通过编译
	}
	...
}

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

template<typename T, std::size_t n>
class SquareMatrix{
public:
	...
	void invert();
};
SquareMatrix<double, 5> sml;
sml.invert();
SquareMatrix<double, 6> sm2;
sm2.invert();

这会具现化两份invert,这是template引出代码膨胀的一个典型例子。

改善方案:创建一个带数值参数的函数

template<typename T>
class SquareMatrixBase{
protected:
	...
	void invert(std::size_t matrixSize);
	...
};
template<typename T, std::size_t n>
clss SquareMatrix: private SquareMatrixBase<T>{
private:
	using SquareMatrixBase<T>::invert;
public:
	...
	void invert(){this->invert(n);}
};
  • 因非类型模板参数而造成的代码膨胀,往往可消除,做法是以函数参数或class成员变量替换template参数。

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

  • 泛化copy构造函数
template<typename T>
class SmartPtr{
public:
	template<typename U>
	SmartPtr(const SmartPtr<U>& other); //copy构造函数
	...
}
class Top{};
class Middle{};
SmartPtr<Top> pt1 = SmartPtr<Middle>(new Middle);

这一类构造函数根据对象u创建对象t(例如根据SmartPtr< U>创建一个SmartPtr< T>),而U和T的类型是同一个template的不同具现体

如果你想要控制copy构造的方方面面,你必须同时声明copy构造函数和“正常的”copy构造函数。

template<class T>
class shared_ptr{
	shared_ptr(shared_ptr const & r);	//copy构造函数
	template<class Y>
	shared_ptr(shared_ptr<Y> const& r);	//泛化copy构造函数
	shared_ptr& operator=(shared_ptr const& r);	//copy assignment
	template<class Y>
	shared_ptr& operator=(shared_ptr<Y> const& r);	//泛化copy assignment
}

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

  • 所有参数之隐式类型转换时,请将那些函数定义为“class template 内部的friend函数”
template<typename T>
class Rational{
public:
	Rational(const T& numberator=0, const T& denominator=1);
	...
};
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;	//错误

错误原因:template实参推导过程不将隐式转换函数纳入考虑。onehalf的类型是Rational,所以T一定是int。但对于2,无法推导出T。
解决办法:将operator* 声明为friend函数

template<typename T>
class Rational{
public:
	...
	friend const Rational operator*(const Rational& lhs, const Rational& rhs)
	{
		...
	}
}

解析:当对象onehalf被声明为一个Rational,class Rational于是被具现化出来,而作为过程的一部分,friend函数operator*接受Rational参数也就被自动声明出来。后者身为一个函数而非函数模板,因此编译器可在调用它时使用隐式转换函数(例如Rational的non-explicit构造函数),而这便是混合式调用之所以成功的原因。

  • 小结:friend函数可以隐式转换,而template成员函数不允许隐式转换

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

  • 5种迭代器分类:
  1. Input:
    Input迭代器只能向前移动,一次一步,客户只可以读取他们所指的东西,并且只能读取一次。

  2. Output:
    Output迭代器只能向前移动,一次一步,客户只可涂写它们所指的东西,而且只能涂写一次。

  3. forward:
    可以做前面两种分类所能做的每一件事,而且可以读或写其所指物一次以上。

  4. Bidirectional:
    比上一个分类威力更大,它除了可以向前移动,还可以向后移动。

  5. random access:
    可以在常量时间内向前或者向后跳跃任意距离。

条款48:认识template元编程

  • 模板元编程TMP:TMP是以C++写成,执行于C++编译器内的程序。

好处:

  1. TMP执行于C++编译器,因此可以将工作从运行期转移到编译期。这导致一个结果是,某些错误原本通常在运行期才能侦测到,现在可以在编译期找出来。
  2. 使用TMP的C++程序可能在每一方面都更高效:较小的可执行文件,较短的运行期,较少的内存需求。然后将工作从运行期转移到编译期的另一结果是:编译时间变长了。
    例子:
template<unsigned n>
struct Factorial{
	enum{ value = n * Factorial<n-1>::value };
};
template<>
struct Factorial<0>{
	enum {value = 1};
};
//调用
int main()
{
	cout << Factorial<5> << endl;
}

用途:

  1. 确保量度单位正确。使用TMP,就可确保在编译期,程序中所有量度单位的组合都是正确的,不论其计算多么复杂。
  2. 优化矩阵运算。
  3. 可以生成客户定制之设计模式。例如在编译期间生成数以百计的智能指针类型。

更细致的讲解:

  1. 程序执行时在编译期,操纵的数据不能是运行变量,只能是编译期常量,不可以修改。
  2. 不能使用if-else for(if-else使用type_traits代替;for使用递归和魔板特化实现)
  3. 由元数据和元函数组成
    元数据:编译常量,不能修改。例:enum枚举常量,静态常量,基本类型,自定义类型
    元函数:处理元数据的构件
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值