读书笔记_Effective_C++_条款四十六:需要类型转换时请为模板定义非成员函数
这个条款可以看成是条款24的续集,我们先简单回顾一下条款24,它说了为什么类似于operator *这样的重载运算符要定义成非成员函数(是为了保证混合乘法2*SomeRational或者SomeRational*2都可以通过编译,2不能同时进行隐式类型转换成某个Rational,再作this用)。
所以我们一般将之定义成友元函数,像下面这样:
class Rational
{
private:
int numerator;
int denominator;
public:
Rational(int n = , int d = ): numerator(n), denominator(d){assert(denominator != );}
int GetNumerator() const{return numerator;}
int GetDenominator() const {return denominator;}
friend const Rational operator* (const Rational& r1, const Rational& r2);
};
const Rational operator* (const Rational& r1, const Rational& r2)
{
return Rational(r1.numerator * r2.numerator, r1.denominator * r2.denominator);
}
现在我们来引入模板,可以像下面这样写,注意这里的operator*是一个独立的模板函数:
template <class T>
class Rational
{
private:
T Numerator;
T Denominator; public:
Rational(const T& Num = , const T& Den = ) : Numerator(Num), Denominator(Den){}
const T GetNumerator() const
{
return Numerator;
} const T GetDenominator() const
{
return Denominator;
} string ToString() const
{
stringstream ss;
ss << Numerator << "/" << Denominator;
return ss.str();
}
}; template <class T>
const Rational<T> operator* (const Rational<T>& a, const Rational<T>& b)
{
return Rational<T>(a.GetNumerator() * b.GetNumerator(),
a.GetDenominator() * b.GetDenominator() );
}
但下面main函数的两行却都不能通过编译:
int main()
{
Rational<int> a(, );
Rational<int> c = a * ; // 不能通过编译!
c = * a; // 不能通过编译!
cout << c.ToString() << endl;
}
原因是编译器推导T出现了困难,a * 2在编译器看来,可以由a是Rational<int>将T推导成int,但是2是什么,理想情况下编译器会尝试将它先转换成一个Rational<int>,并将T推导成int,但事实上编译器在“T推导过程中从不将隐式类型转换函数纳入考虑”。所以无论是a * 2还是2 * a都是不能通过编译的,一句话,隐式转换+推导T不能被同时被编译器接受。
解决问题的思路便接着产生,编译器既然不能同时接受这两个过程,就让它们事先满足好一个条件,再由编译器执行另一个过程好了。
如果把这个operator*放在template class里面,也就是先在生成模板类的那一步就定下T,这样编译器只要执行隐式转换这一步就可以了。
因此我们可以这样来改:
template <class T>
class Rational
{
…
friend Rational operator* (const Rational& a, const Rational& b);
}; template <class T>
const Rational<T> operator* (const Rational<T>& a, const Rational<T>& b)
{
// 这里友元函数的声明并不是用来访问类的私有成员的,而是用来进行事先类型推导的
return Rational<T>(a.GetNumerator() * b.GetNumerator(),
a.GetDenominator() * b.GetDenominator() );
}
注意红色部分,我们添加了一个友元函数的声明,果然编译通过了,但链接时又报错了,原因是链接器找不到operator*的定义,这里又要说模板类中的一个特殊情况了,它不同与普通的类,模板类的友元函数只能在类中实现,所以要把函数体部分移至到类内,像下面这样:
template <class T>
class Rational
{
…
friend Rational operator* (const Rational& a, const Rational& b)
{
return Rational (a.GetNumerator() * b.GetNumerator(),
a.GetDenominator() * b.GetDenominator());
}
…
}
这下编译和链接都没有问题了。这里还要说一下,就是移至类内后,T的标识符可以不写了,但如果非要写成下面这样,自然也是OK的。
friend Rational<T> operator* (const Rational<T>& a, const Rational<T>& b)
{
return Rational<T>(a.GetNumerator() * b.GetNumerator(),
a.GetDenominator() * b.GetDenominator());
}
operator*里面只有一句话,但如果friend函数里面的东西太多了,可以定义一个辅助方法,比如DoMultiply(),这个DoMultiply可以放在类外去实现,DoMultiply本身不支持混合乘法(2 * SomeRational或者SomeRational * 2),但由于在operator*里面已经进行了隐式类型转换,所以到DoMultiply这一级是没有问题的。
最后总结一下:
当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”。
读书笔记_Effective_C++_条款四十六:需要类型转换时请为模板定义非成员函数的更多相关文章
- 读书笔记_Effective_C++_条款三十六:绝不重新定义继承而来的non-virtual函数
这个条款的内容很简单,见下面的示例: class BaseClass { public: void NonVirtualFunction() { cout << "BaseCla ...
- 读书笔记_Effective_C++_条款四十九:了解new_handler的行为
本章开始讨论内存分配的一些用法,C/C++内存分配采用new和delete.在new申请内存时,可能会遇到的一种情况就是,内存不够了,这时候会抛出out of memory的异常.有的时候,我们希望能 ...
- 读书笔记_Effective_C++_条款四十八:了解模板元编程
作为模板部分的结束节,本条款谈到了模板元编程,元编程本质上就是将运行期的代价转移到编译期,它利用template编译生成C++源码,举下面阶乘例子: template <int N> st ...
- 读书笔记_Effective_C++_条款四十五:运用成员函数模板接受所有兼容类型
比如有一个Base类和一个Derived类,像下面这样: class BaseClass {…}; class DerivedClass : public BaseClass {…}; 因为是父类与子 ...
- 读书笔记_Effective_C++_条款二十六:尽可能延后变量定义式的出现时间
这个条款从字面意思还是很好理解的,就是在使用这个变量前才去定义,而不是很早就定义了它,而在很后面的时候才去使用.这个条款只适用于对变量声明位置没有要求的语言,比如C++.对于像C或者一些脚本语言,语法 ...
- 读书笔记_Effective_C++_条款四十四:将与参数无关的代码抽离template
标题上说“将与参数无关的代码抽离template”,这里的参数既可以指类型,也可以是非类型,我们先来看看非类型的情况. 假定我们要为矩阵写一个类,这个矩阵的行列元素个数相等,是一个方阵,因而我们可以对 ...
- 读书笔记_Effective_C++_条款四十二:了解typename的双重意义
顾名思义,typename有双重含意.只要你用过template,那么第一重含意一定知道,那就是声明模板的时候,我们既可以这样写: template <class T> 也可以这样写 te ...
- 读书笔记_Effective_C++_条款四十:明智而审慎地使用多重继承
多重继承是一种比较复杂的继承关系,它意味着如果用户想要使用这个类,那么就要对它的父类也了如指掌,所以在项目中会带来可读性的问题,一般我们都会尽量选择用单继承去替代它. 使用多重继承过程容易碰到的问题就 ...
- Effective C++ -----条款46:需要类型转换时请为模板定义非成员函数
当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”.
随机推荐
- 自旋锁-SpinLock(.NET 4.0+)
短时间锁定的情况下,自旋锁(spinlock)更快.(因为自旋锁本质上不会让线程休眠,而是一直循环尝试对资源访问,直到可用.所以自旋锁线程被阻塞时,不进行线程上下文切换,而是空转等待.对于多核CPU而 ...
- H5图片裁剪升级版(手机版)
前段时间做了个跟裁剪相关的活动<用H5中的Canvas等技术制作海报>,这次公司要做个与奥运相关的活动,扫车牌赢奖. 于是我就在上一个活动的基础上,将代码重新封装一下,并且将计算方式写的更 ...
- CentOS下搭建NFS服务器总结
环境介绍: . 服务器: 192.168.0.100 . 客户机: 192.168.0.101 安装软件包: . 服务器和客户机都要安装nfs 和 rpcbind 软件包: yum -y instal ...
- sublime text配置记录
代码编辑器有一直都有在尝试新的,如brackets/vs code/webstrom,最后还是用回sublime,每次要重新安装的时候都需要上网搜索相关配置资料,特些记录,以备下次使用: 下载地址 下 ...
- 注册表法修改IE8安全级别的方法
最近写客户端工具修复IE浏览器的一些功能,下面是网上找到的东东 1.将当前用户IE安全级别设置为“中”: Windows Registry Editor Version 5.00 [HKEY_CURR ...
- 无线局域网络 WIFI/WAPI/WLAN区别浅析
WIFI和WAPI的区别 既然WIFI和WAPI都是WLAN的传输协议,那么两者究竟都有怎样的区别? 首先第一点区别在于,两者的缔造者不一样.WIFI是又国外制定的一个协议,而WAPI是由中国制定的, ...
- linux web服务器,防火墙iptables最简配置
配置防火墙(服务器安全优化) 安全规划:开启 80 22 端口并 打开回路(回环地址 127.0.0.1) # iptables –P INPUT ACCEPT # iptables –P OUTP ...
- 转载:Cellebrite携两大移动数据服务强势来华
[IT168专稿]随着移动互联网的发展,智能终端也越来越普及,围绕整个移动互联网的产业链产生了巨大的商机.有这么一家做移动数据传输服务的厂商,他们一直专注在移动领域,为运营商和零售商以及司法部门提供服 ...
- 《STL系列》之map原理及实现
上一篇文章<STL系列>之vector原理及实现,介绍了vector的原理及实现,这篇文章介绍map的原理及实现.STL实现源码下载.STL中map的实现是基于RBTree的,我在实现的时 ...
- [C] zlstdint(让VC、TC等编译器自动兼容C99的整数类型)V1.0。支持Turbo C++ 3等DOS下的编译器
作者:zyl910 以前我曾为了让VC++等编译器支持C99的整数类型,便编写了c99int库来智能处理(http://www.cnblogs.com/zyl910/p/c99int_v102.htm ...