条款5:对定制的“类型转换函数”保持警觉

c++允许编译器在不同类型之间执行隐式转换,方法有两种:单自变量构造函数和隐式类型转换操作符。

单自变量构造函数:可能声明拥有单一参数,也可能声明拥有多个参数,并且除了第一参数之外都有默认值。

class Name
{
public:
	Name(const string& s);
};

class Rational
{
public:
	Rational(int numerator = 0, int denominator = 1);
};

隐式类型转换操作符:关键词operator之后加一个类型名称。

class Rational
{
public:
	operator double() const;
};

这个函数会在下面情况被调用,第二行的r会被隐式转化为double类型:

	Rational r(1, 2);
	double b = 0.5 * r;

为什么最好不要提供任何类型转换函数,根本问题在于,在你从未打算也未预期的情况下,此类函数可能会被调用,而其结果可能是不正确、不直观的程序行为,很难调试。

 

 

例子1:

Rational r(1,2);
cout<<r<<endl;//应该输出1/2

假设你忘记为这个Rational写一个operator<<,你可能希望在调试或者运行的时候报错,但是事实上并没有报错。当编译器发现不存在任何operator<<可以接受Rational,它会想尽办法让这个参数调用成功。比如调用Rational::operator double(),将r隐式转化为double,这样上述cout会以浮点数方式将r输出。

解决方法,就是用函数取代类型转换操作符。比如:

class Rational
{
public:
    double asDouble() const;
};

用成员函数明确调用:

Rational r(1,2);
cout<<r;//错误,Rational 没有 operator<<
cout<<r.asDouble();

标准程序库的string类型并未含有“从string object到C-style char *的隐式转换函数”,他们提供的办法是用一个显式的成员函数string.c-str()来执行上述行为。

 

 

例子2:

template <class T>
class Array
{
public:
	Array(int lowBound, int highBound);
	Array(int size);
	T& operator[](int index);
	
};
bool operator==(const Array<int> &lhs, const Array<int> &rhs);

int main ()
{
    Array<int> a(10);
    Array<int> b(10);

    for (int i = 0; i < 10; i++)
    {
    	if (a == b[i])
	{
		//do something
	}
	else
	{
		//do something
	}
    }
    return 0;
}

主函数里面应该是a[i]==b[i],但是忘记写[i],此时编译器并没有报错。它会同过调用Array::Array(int size)将b[i]隐式转换为Array,然后每次迭代都用a的内容和这个数组比较。这不仅没有实现功能,并且很没有效率,因为必须产生和销毁这个临时变量。

解决办法:

使用C++特性:关键词explicit。只要将构造函数声明为explicit,编译器便不能因隐式转换而调用他们,不过显式转换还是允许的;

template <class T>
class Array
{
public:
	Array(int lowBound, int highBound);
	explicit Array(int size);
	T& operator[](int index);
	
};
bool operator==(const Array<int> &lhs, const Array<int> &rhs);
int main()
{
        Array<int> a(10);
	Array<int> b(10);
	if (a == b[0])//不行,无法隐式转换
	{

	}
	if (a == Array<int>(b[0]))//可行,显示调用构造函数
	{

	}
	if (a == static_cast<Array<int>>(b[0]))// c++类型转换函数,可行,但是功能让人怀疑
	{

	}
	if (a == (Array<int>) (b[0]))//c旧式转型可行,但是功能让人怀疑
	{

	}
return 0;
}

下面,再次考虑Array template,你需要一种方法,不但允许以一个整数作为构造函数自变量指定数组大小,又能阻止一个整数被隐式转换为一个临时Array变量。

template <class T>
class Array
{
public:
	class ArraySize
	{
	public:
		ArraySize(int numElements) :theSize(numElements){}
		int size() const { return theSize; }
	private:
		int theSize;
	};
	Array(int lowBound, int highBound);
	explicit Array(ArraySize size);
	T& operator[](int index);
	
};

当我们运行下面代码时:

Array<int> a(10)

编译器先将int转为临时的ArraySize变量,再调用构造函数。

但是如果运行下面代码:

Array<int> a(10);
Array<int> b(10);
for (int i = 0; i < 10; i++)
if (a == b[i])
{

}

上面a==b[i]是无法成功的。编译器不能考虑现将int转换为临时的ArraySize对象,再产生Array对象,因为隐式转换只会调用一次。

为什么最多只能调用一次隐式转换?

①编码人员更容易出错了,代码检查困难。
②如果需要A→D,但是有多种途径,那是从A→B→D还是A→C→D呢,可能是不一样的。
③一次转换就让人头疼了,所以explicit关键字都出来了,人们更倾向于显式类型转换。
④编辑器&编译器更复杂啊喂,类型检查难度大增。
⑤如果允许了2次,你还会问为什么不允许3次?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Simon|

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值