当前位置:网站首页>解析隐式类型转换操作operator double() const,带你了解隐式转换危害有多大
解析隐式类型转换操作operator double() const,带你了解隐式转换危害有多大
2022-08-06 12:06:00 【林夕07】
前言
我首次看到这种函数的时候是在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(代理类)。
边栏推荐
- 纯色山鹪莺
- 自动化测试是什么?应用在哪儿?
- Golang gin 配置腾讯云cos实现单文件与多文件上传
- How to upgrade Kubernetes gracefully
- 电脑嗡嗡响怎么回事?电脑发热嗡嗡响原因及解决方法
- MySQL statistics are not allowed to cause performance problems
- d找包中函数
- AlexNet—论文分析及复现
- Structure of the actual combat battalion module nine operations
- The entry node of the ring in the NC3 linked list
猜你喜欢

NASA suspends all spacewalks on the International Space Station due to safety issues with spacesuits

Teach you to draw pixel art and share 195 issues every week

哈希表 | 基础知识总结

locust做性能测试

PS6603-USB PD 协议 SINK 端输出控制器芯片

kubernetes灰度发布

STM32 startup process - startup_xxxx.s file analysis (MDK and GCC dual environment)

PS6603 代理直销Type-C PD 电源传输接收 SINK 端控制器芯片

数字化转型怎么就那么的难?!

How to find stills?Where can I get HD resources?It's too late to meet these 9 website channels!original
随机推荐
Introduction to SQLNET.ALLOWED_LOGON_VERSION parameter in SQLNET.ORA
Ansible自动化运维、ZABBIX监控
哈希表 | 两个数组的交集 | leecode刷题笔记
PS6603-USB PD protocol SINK terminal output controller chip
电脑嗡嗡响怎么回事?电脑发热嗡嗡响原因及解决方法
CISP-PTE Practical Exercises Explanation One (New Version)
Draw timing diagrams with code!YYDS
机器学习实战-梯度下降法在线性回归模型中的使用
XML usage
A Brief Introduction to Type Assertion in Go
Kubernetes DevOps 工具
从ADVANCE.AI 全球产品负责人周洪丞的发言中了解其如何通过产品赋能中国出海企业
SQL 注入复习总结
shutdown procedure
PHP fopen write file content
Office宏上线Cobalstrike
【SSL集训DAY1】C【暴力】【数学】
kubernetes日常命令
Guitar Pro8 Guitar software update log is introduced
The digital transformation of how so difficult?!