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

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

对条款24的例子进行模板化:

#include<iostream>
using namespace std;

template<typename T>
class Rational{
public:
	Rational(const T& n = 0, const T& d = 1) :numerator(n), denominator(d){}//构造函数刻意不为explicit,为了隐式类型转换
	const T getNumerator() const{ return numerator; }
	const T getDenominator() const{ return denominator; }
private:
	T numerator;
	T denominator;
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs){
	return Rational<T>(lhs.getNumerator()*rhs.getNumerator(), lhs.getDenominator()*rhs.getDenominator());
}
int main(){
	Rational<int> oneEighth(1, 8);
	Rational<int> oneHalf(1, 2);
	Rational<int> result = oneHalf*oneEighth;
	//result = oneHalf * 2;//无法通过编译
	system("pause");
	return 0;
}

上述例子中result = oneHalf * 2; 无法通过编译的原因在于实参推导,对oneHalf进行推导,operator*的第一个参数被声明为Rational<T>,而传递的第一个参数是Rational<int>,所以T一定是int;operator*的第一个参数被声明为Rational<T>,而传递的第一个参数是2,因为在template实参推导过程中并不会考虑采纳“通过构造函数而发生的”隐式类型转换,所以2无法转换成Rational<int>进而推导出T为int。

利用如下事实就可以解决上述问题:template class内的friend声明式可以指涉某个特定函数。class template并不依赖template实参推导(后者只施行于function template身上),所以编译器总是能在classRational<T>具现化时得知T。

#include<iostream>
using namespace std;

template<typename T> class Rational;
template<typename T>
const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs);

template<typename T>
class Rational{
public:
	Rational(const T& n = 0, const T& d = 1) :numerator(n), denominator(d){}
	const T getNumerator() const{ return numerator; }
	const T getDenominator() const{ return denominator; }
	friend const Rational operator*(const Rational& lhs, const Rational& rhs){ return doMultiply(lhs, rhs); }
private:
	T numerator;
	T denominator;
};

template<typename T>
const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs){
	return Rational<T>(lhs.getNumerator()*rhs.getNumerator(), lhs.getDenominator()*rhs.getDenominator());
}
int main(){
	Rational<int> oneEighth(1, 8);
	Rational<int> oneHalf(1, 2);
	Rational<int> result = oneHalf*oneEighth;
	result = oneHalf * 2;
	cout << result.getNumerator() << "/" << result.getDenominator() << endl;

	system("pause");
	return 0;
}

上述例子中的技术虽然使用了friend,却与传统的friend用途“访问class的non-public成分”毫不相干。为了让类型转换可能发生与所有实参身上,我们需要一个non-member函数(条款24);为了让这个函数被自动具体化,我们需要将它声明在class内部;而在class内部声明non-member函数的唯一办法就是让它成为一个friend。

请记住:

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

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

1、STL共有5种迭代器:

a、input迭代器,只能向前移动,一次一步。客户只能读取它所指的对象,且只能读取一次。它模仿指向输入文件的阅读指针(read pointer);C++程序中的istream_iterators就是这类的代表。

b、output迭代器,只能向前移动,一次一步。客户只能写入它所指的对象,且只能写入一次。它模仿指向输出文件的涂写指针(write pointer);ostream_iterators是这一类代表。

c、forward迭代器,继承自input迭代器,可以做上述两种迭代器能做的每一件事,而且可以读写它所指的对象一次以上。

d、bidirectional迭代器,继承自forward迭代器,可以向前向后移动。STL中的list、set、multiset、map、和multimap迭代器就是这一类迭代器。

e、randomaccess迭代器,继承自bidirectional迭代器。它可以在常量时间内向前或向后跳跃任意距离,类似原始指针,内置指针就可以当做random access迭代器使用。vector、deque和string的迭代器就是这类。

2、例子:

//deque迭代器是random access迭代器,所以应该这样:
template<...>
class deque{
public:
	class iterator{
	public:
		typedef random_access_iterator_tag iterator_category;
		...
	};
	...
};
//list迭代器可双向行进,所以应该这样:
template<...>
class list{
public:
	class iterator{
	public:
		typedef bidirectional_iterator_tag iterator_category;
		...
	};
	...
};
//对于iterator_traits,只要响应iterator class的嵌套式typedef即可:
template <typename IterT>
struct iterator_traits{
	typedef typename IterT::iterator_category iterator_category;
	...
};
//偏特化,以iterator_traits为指针指定的迭代器类型如下:
template<typename IterT>
struct iterator_traits<IterT*>{
	typedef random_access_iterator_tag iterator_category;
	...
};
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT& d){
	if (typeid(typename std::iterator_traits<IterT>::iterator_category)
		== typeid(std::random_access_iterator_tag))
	...
}

上述例子说明设计并实现traits class的步骤是:

a、确认若干希望将来可取得的类型相关信息。例如对迭代器而言,希望将来可取得其分类。

b、为该信息选择一个名称(例如iterator_category)。

c、提供一个模板和一组特化版本(如iterator_traits),内含希望支持的类型相关信息。

3、上述例子IterT类型在编译期间获取,所以iterator_traits<IterT>::iterator_category也可在编译期间确定。但if语句却在运行期核定。怎么办?可以使用重载的办法。

例子:

//这份实现用于random access迭代器
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, std::random_access_iterator_tag){
	iter += d;
}
//这份实现用于bidirectional迭代器
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, std::bidirectional_iterator_tag){
	if (d >= 0){
		while (d--){
			++iter;
		}
	}
	else {
		while (d++){
			--iter;
		}
	}
}
//这份实现用于input迭代器
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, std::input_iterator_tag){
	if (d < 0){
		throw std::out_of_range("Negative distance");
	}
	while (d--){
		++iter;
	}
}
//重载机制调用适当的实现代码
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT& d){
	doAdvance(iter, d, typename std::iterator_traits<IterT>::iterator_category());
}

上述例子说明使用一个traits class的方法是:

a、建立一组重载函数或函数模板,彼此间的差异只在于各自的traits参数,令每个函数实现代码与其接受的traits信息相应和。

b、建立一个控制函数或函数模板,它调用上述重载函数并传递traits class所提供的信息。

请记住:

  • Traits classes 使得“类型相关信息”在编译期可用。它们以templates和 template特化完成实现。
  • 整合重载技术后,traitsclasses 有可能在编译器对类型执行if...else 测试。

Effective C++ 条款 48:认识template元编程

1、template metaprogramming(模板元编程)是编写template-based c++程序并执行于编译期的过程。是以c++写成,执行于c++编译器内的程序。

2、模板元编程执行与C++编译期,因此可将工作从运行期转移到编译期,导致的一个结果是某些错误原本在运行期才能侦测到,现在可在编译期找出来,另一个结果是使用TMPC++程序可能在每一方面更高效:较小的可执行文件、较短的运行期、较少的内存需求。但是将工作从运行期转移到编译期的结果是编译时间能变长了

3、模板元编程是个图灵完全机器,可以声明变量、执行循环、编写及调用函数……模板元编程并没有真正的循环构件,循环由递归(recursion)完成。TMP递归甚至不是正常的递归,因为TMP递归不涉及递归函数调用,而是涉及“递归模板具现化”(recursive template instantiation)。

例子:

#include<iostream>
using namespace std;

template<unsigned int n>
class Factorial{
public:
	enum{ value = n*Factorial<n - 1>::value };
};
template<>
class Factorial<0>{
public:
	enum { value = 1 };
};

int main(){
	cout << Factorial<5>::value << endl;
	cout << Factorial<10>::value << endl;

	system("pause");
	return 0;
}

请记住:

  • Template metaprogramming(TMP,模板元编程)可将工作由运行期移到编译期,因而得以实现早期错误侦测和更高的执行效率。
  • TMP可被用来生成“基于政策选择组合”(based on combinations of policy choices)的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值