不得不说, 符号重载使C++实现了很多优秀的功能。
优点:
比如 智能指针,依赖对指针的赋值来计数,自动析构减少计数,防止指针内存泄露
比如 让代码更简洁,用一个简单的符号 如[], < , ==, () 帮助程序员完成了很多需要手写的函数。写java的一定深有体会
缺点:
把大量问题隐藏在重载的代码中。就好比一个总是溺爱女儿的老父亲,女儿不喜欢释放内存,那我就帮你保护起来。问题的本质是,不按规矩写代码,用再多的补救措施,那个不按规矩来的人依然是漏洞百出的。
再来说代码调试,重载符号的函数怎么跟进怎么看,是不是很麻烦。哪里的一个看是普通的一行代码,蕴含了大量重载符号函数的操作,不禁让程序员吓一跳,就这一行代码怎么发生这么多我不知道的事,还是老老实实写好函数名称,老老实实调用函数接口吧。
看看std boost 模板库里,让人眼花缭乱的源码,都不谈可读性和可理解性,这难道是我们对优秀的定义?不禁困惑,如果我们自己设计通用的模板库,也会把代码写成这样吗?
有幸对比了纯C语言的 list tree 等的纯宏实现,简洁明了可读性高,虽然可能编码习惯和安全性每个人不一样,但对比std容器简直好太多。
举一个例子:
std:set 默认有序,采用红黑树有序存储。可用 < 比较器 和 () 来实现唯一性。 隐含的规则如下
1. f(x,x) = false;
2. if f(x,y) then !f(y,x)
3. if f(x,y) and f(y,z) then f(x,z)
4. if !f(x,y)&&!f(y,x) then x==y; if x==y and y==z then x==z;
set容器在判定已有元素a和新插入元素b是否相等时,如果采用 less比较器 bool operator() (const myclass& op),是这么做的:
1)将a作为左操作数,b作为有操作数,调用比较函数,并返回比较值
2)将b作为左操作数,a作为有操作数,再调用一次比较函数,并返回比较值。
如果1、2两步的返回值都是false,则认为a、b是相等的,则b不会被插入set容器中;
如果1、2两步的返回值都是true,则可能发生未知行为,
因此,记住一个准则:永远让比较函数对相同元素返回false。
如果有()重载 就使用这个比较傲规则,同样 永远让比较函数对相同元素返回false。
class vector_cmp{
public:
bool operator()( const std::vector<int> v1,const std::vector<int> v2)
{
if (v1.size() != v2.size())
return v1.size() < v2.size();
if (v1.size() != 3)
return false;
if (v1[0] == v2[0] && v1[1] == v2[1] && v1[2]==v2[2])
return false;
if (v1[0] != v2[0])
return v1[0] < v2[0];
if(v1[1] != v2[1])
return v1[1] < v2[1];
if (v1[2] != v2[2])
return v1[2] < v2[2];
return false;
}
};
void insert_set(std::set<std::vector<int>, vector_cmp>& myset)
{
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = {1, 2, 4};
std::vector<int> v3 = {1, 2, 3};
std::vector<int> v4 = {3, 4, 8};
myset.insert(v1);
myset.insert(v2);
myset.insert(v3);
myset.insert(v4);
}
上面这个 set 希望存储不重复的三元组,假设三元组是已经排序好的。 那么问题是, 插入 v1、v2、v3、v4的时候,重载函数()分别运行几次。
答案是0,3,3,4
in operator ()(1,2 , 4)===(1,2 , 3)
in operator ()(1,2 , 3)===(1,2 , 4)
in operator ()(1,2 , 4)===(1,2 , 3)
in operator ()(1,2 , 3)===(1,2 , 3)
in operator ()(1,2 , 3)===(1,2 , 4)
in operator ()(1,2 , 3)===(1,2 , 3)
in operator ()(3,4 , 8)===(1,2 , 3)
in operator ()(3,4 , 8)===(1,2 , 4)
in operator ()(1,2 , 4)===(3,4 , 8)
in operator ()(3,4 , 8)===(1,2 , 4)
总结:
回首细看标准库的源码,再对比现有的优秀的开源C++框架,很明显的问题是开源的普遍在符号重载上有意退避。回想起十年前,搞编译器、系统编译优化的老教授,语重心长的在课堂上大肆批评符号重载,我们还在疑惑是不是他老了接收不了新事物,现在我不怀疑。
凡事有利必有蔽,双刃剑还得利用优良特性,同时避其糟粕。