条款24:若所有参数需要类型转换,请为此采用no-member函数

本条款的核心内容是:如果一个函数的所有参数(包括this指针所指的那个隐喻的参数)需要进行类型转换的时候,那么这个函数必须是非成员函数。当然这句话只是内含真理,并不是说就是真理,因为在加入模版编程的时候,就会又有一些新争议,新解法,以及令人惊讶的一些设计。这些将形成了条款46

下面我来详细介绍本条款:

首先我定义如下一个类

class Rational
{
private:
	int _x;
	int _y;
public:
	Rational(int numerator = 0, int denominator = 1)
		:_x(numerator)
		, _y(denominator){}
};
如果我向让此类对象可以进行乘法运算的话,我们应该加上operator*函数,但是如果你不知道该选用成员函数,非成员函数,还是友元函数来实现的时候,你就保持面向对象的精神,即数据和其操作方法绑定在一起,所以先选成员函数来实现,虽然这个和之前所讲的(使用成员函数会降低封装性有一些矛盾),我们先放下这个矛盾,继续往后看,

所以加入operator*函数以后得到如下代码:

class Rational
{
private:
	int _x;
	int _y;
public:
	Rational(int numerator = 0, int denominator = 1)
		:_x(numerator)
		, _y(denominator){}
	const Rational operator*(const Rational& rhs)
	{
		return (this->_x*rhs._x, this->_y*rhs._y);
	}
};
然后我们可以进行如下操作都没有问题

Rational one(1, 8);
	Rational two(1,2);
	Rational result = one*two;
	result = result*one;
但是上述的简单的应用不能满足我们的要求,既然是乘法运算就应该支持混合运算等一系列的问题,所以我们来看下面的来两个用法

①result = one * 2;
②result = 2 * one;
①顺利编译通过而且一切正常,但是②就出问题了,编译布不能通过为什么呢?

下面我来解释:

首先将①②写成最基础的调用方式

①result = one.operator*(2);
②result = 2.operator*(one);
one是一个内含operator的class 的对象,所以编译器成功调用operator*()函数,但是整数2不是class的对象,所以也就没有operator*()函数,但是编译器还是会去尝试寻找可以被调用的operator*()函数(也就是在命名空间或者全局作用域内),但是这个代码中并没有符合要求即接受一个int 和Rational作为参数的函数,所以会失败。

下面我们来说说①调用成功的这个,是不是很好奇operator*()里面接受的参数是Ratioal类型的怎么传过去一个整型也可以呢?

解释:因为这里发生了隐式类型转换,编译器直到你传递了一个int类型的,而operator*()函数需要的是Rational类型的,所以编译器就调用Ratianal的构造函数类使用你所传的Int类型的值即2作为参数,创建了一个合适Rational对象给operator*()函数,即创建了一个临时对象传递过去。类似于编译器做了这样的动作

const Rational temp(2);
result = one*temp;
当然这是因为我们的构造函数声明为non-explicit,即允许隐式类型转换所以编译器才进行那些动作,①才能通过,如果将构造函数声明为explicit的话,则①也不能通过编译。

如果这样做了就很难让这个Rational这个类支持混合运算了,但是至少保持上面两个句子的一致性了。


当然我们的目标不仅仅在一致性上,我们需要他即一致又支持混合运算,也就是说希望有个设计使上面两个句子都可以编译通过。在这里先阐述一个续上面例子的问题,即为什么声明Rational的构造函数为non-exolicit,但是还是只有①能通过,而②不能呢?

解释:因为只有当参数被列于参数列内,这个参数才是隐式类型转换的合格参与者。这里调用这个成员函数的对象,即this指针所隐函的参数,绝不是合格的参与者,这里明白了为什么②不可以了吧,因为②的2是调用函数的对象,不是隐式类型转换合格的参与者,所以失败。

好了该解释的都解释完了,下面我来说一下如何解决上述出现的问题,即如何满足一致性和支持混合运算,那就是让operator*()成为一个非成员函数

class Rational
{
private:
	int _x;
	int _y;
public:
	Rational(int numerator = 0, int denominator = 1)
		:_x(numerator)
		, _y(denominator){}
	 int rex() const 
	{
		return _x;
	}
	 int rey() const 
	{
		return _y;
	}
};
const Rational operator*(const Rational& lhs,const Rational& rhs)
{
	return Rational(lhs.rex()*rhs.rex(), lhs.rey()*rhs.rey());
}
因为写成非成员函数的话,成员是私有的所以我们加上了访问成员的函数rex()和rey()(注意:这两个函数必须是const 类型的,因为对象类型是const的,不加的话类型就不兼容了)。

这个时候

result = one * 2;
result = 2 * one;
这两个都能编译通过了。


最后还有一点要说的是,为什么不写成友元函数的形式呢?

解释:成员函数的反面是非成员函数不是友元函数。如果与某个类相关的函数的参数都需要类型转换,则无法使用成员函数的情况,就想到了友元函数,这是一种过于牵强的思想,无论何时,该避免友元函数就避免友元函数,因为就像现实世界一样,朋友多往往其麻烦多余其价值。所以我们要正确的理解,不是说函数不该成为成员函数就应该让函数成为友元函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值