解析隐式类型转换操作operator double() const,带你了解隐式转换危害有多大
Posted 林夕07
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了解析隐式类型转换操作operator double() const,带你了解隐式转换危害有多大相关的知识,希望对你有一定的参考价值。
目录
前言
我首次看到这种函数的时候是在Flightgear飞行模拟器
的源码中。再就是我今天在《More Effective C++》中条款五:对定制的“类型转换函数”保持警觉
中看到的。这种函数叫类型转换类型,也是隐式类型转换操作符。
operator double() const
return m_num;
隐式类型转换操作符
用法:operator 类型名 [const]
注意:这个函数不能指定返回值类型。因为返回值类型在函数名上表现出来了。
我们看下面这个例子, 有三个隐式转换函数 都是将 Rational
转换为 double
。
class Rational
public:
Rational(int numerator = 0, int denominator = 1)
if(0 == denominator)
m_num = 0;
return;
m_num = static_cast<double>(numerator) / denominator;
double operator--()
return m_num -= 1;
double operator++()
return m_num += 1;
operator double() const
return m_num;
private:
double m_num;
;
int main()
Rational r(1, 2);
double d = 0.5 * r; // 将r转换为double 然后执行乘法运算。
cout << d << endl;
cout << ++d << "\\t" << d << endl;
cout << d++ << "\\t" << d << endl;
cout << --d << "\\t" << d << endl;
cout << d-- << "\\t" << d << endl;
return 0;
运行结果:
使用注意
仔细看下面这段代码,cout << r;
我们前面并没有重写operator<<
函数。但是下面的代码可以正常运行。
int main()
Rational r(1, 2);
cout << r;
return 0;
运行结果:
编译器在发现你没有写operator<<
函数时,编译器会想尽办法去找一系列可接受的隐式类型转换 让函数执行起来。
这将会导致程序出现意想不到的结果。
解决方案
我们以功能对等的另一个函数取代类型转换操作符。我们可以自己写个asDouble
的函数代替operator double()
。
看下面这段代码:
class Rational
public:
Rational(int numerator = 0, int denominator = 1)
if(0 == denominator)
m_num = 0;
return;
m_num = static_cast<double>(numerator) / denominator;
//operator double() const
//
// return m_num;
//
double asDouble() const
return m_num;
private:
double m_num;
;
int main()
Rational r(1, 2);
cout << r;
return 0;
运行结果:
可以看到程序已经报错了。
正常调用我们写的转换函数就可以正常实现功能。
深思
我们必须明白调用函数转换函数虽然不是很方便,但是可以避免“默认调用那些其实并不打算调用的函数
”的错误。越有经验的程序员越要注意避免使用这种类型转换操作符。你要知道STL库中的string容器为什么不实现从string object
到 char*
的隐式转换函数。而是使用c_str的成员函数吗?要知道他们都是非常老练的程序员了吧。
构造函数造成的隐式转换
单参数的构造函数也能实现隐式转换,而且难为发现。这可比隐式转换操作符难处理多了。
下面实现了一个模板数组类Array,并提供了俩种初始化方式、元素个数、下标运算符等。还实现了Array类的关系运算符(注:该函数仅仅为了测试效果,此处不谈函数功能是否正确)
。
template<class T>
class Array
public:
Array(int lowBound, int highBound)
if(lowBound > highBound)
return;
m_arr.resize(highBound - lowBound);
for (size_t i = lowBound; i <= highBound; ++i)
m_arr[i - lowBound] = i;
Array(int size)
if(size > 0)
m_arr.resize(size);
size_t size() const
return m_arr.size();
const T& operator[](size_t index) const
return m_arr[index];
T& operator[](size_t index)
return m_arr[index];
private:
vector<T> m_arr;
;
bool operator==(const Array<int>& lhs, const Array<int>& rhs)
for (size_t i = 0; i < lhs.size() && i < rhs.size(); ++i)
if(lhs == rhs[i])
cout << "相同" << endl;
else
cout << "不相同" << endl;
return true;
上述代码看着挺正常的吧,但是下面的测试代码和测试结果你绝对会吃惊。
int main()
Array<int> arr1(2, 4);
Array<int> arr2(5);
for(size_t i = 0; i < arr1.size(); ++i)
cout << arr1[i] << "\\t";
cout << endl;
for (size_t i = 0; i < arr2.size(); ++i)
cout << arr2[i] << "\\t";
cout << endl;
arr1 == arr2;
return 0;
arr1数组里面的元素是2,3,4,arr2数组里面的元素是0,0,0,0,0。很明显没有一个元素是相同的,但事实就是打印了三次相同。 但是我想如果你仔细观察这个函数的比较表达式你就会发现,我少写了一个下表运算符。
没错,它应该是lhs[i] == rhs[i]
这样的。但是当我意外的漏掉下表运算符时,编译器居然没有任何的抱怨????
如果你的编译器足够智能那么你会发现有这么一句提醒 使用构造函数array (int size)
,用户自定义将rhs[i]
从int
类型转换为’array<int>
类型 。
分析
编译器没有找到对于版本的关系运算符,但是编译器只需要调用Array<int>的构造函数(需要一个int变量),就可以将int转换为Array<int> 类型。所有就变成了lhs == Array<int>(rhs[i])
。姑且抛开这个错误不谈,在这个循环中每一个的进行比较都会产生和释放一个临时的Array<int>对象 效率极低。
总结
虽然我们不声明隐式类型转换操作符,就可以避免一些不必要的一些麻烦,但是单变量的构造函数却防不胜防,因为你极有可能要为用户提供这个一个功能,但你又不想让编译器不分青红皂白的调用这个构造函数,
解决方案
explicit关键字
使用explicit
关键字修饰 关闭函数的类型自动转换(防止隐式转换) 用法简单。
explicit Array(int lowBound, int highBound)
if(lowBound > highBound)
return;
m_arr.resize(highBound - lowBound + 1);
for (size_t i = lowBound; i <= highBound; ++i)
m_arr[i - lowBound] = i;
explicit Array(int size)
if(size > 0)
m_arr.resize(size);
这样就会提示没有对应的关系运算符。
引入Proxy classes 代理类
为了避免int类型变量的隐式类型转换,我们可以将int类型的变量封装为一个proxy classes。用新类来保存要被产生数组的大小。
class ArraySize
public:
ArraySize(size_t numElements) :m_size(numElements)
size_t size() const
return m_size;
private:
size_t m_size;
;
我们将这个proxy classes放到Array的public作用域下即可,然后我们更新单参数的构造函数。
同样编译器将会报错 提示没有对应的关系运算符。
总结
- 避免使用隐式类型转换操作符,使用转换函数代替。
- 避免使用单参数的构造函数,可以使用explicit关键字和proxy classes(代理类)。
以上是关于解析隐式类型转换操作operator double() const,带你了解隐式转换危害有多大的主要内容,如果未能解决你的问题,请参考以下文章