C++运算符重载代码分析
来源:互联网 发布:200万网络摄像机一夜 编辑:程序博客网 时间:2024/06/05 11:08
0. 说明
通过示例代码,说明运算符重载的调用时机,以及printf的参数处理机制。
1. 示例代码
#include <cstdio>#include <iostream>class Foo {public: explicit Foo(int v): value(v) { } operator int() { std::cout << "int()" << std::endl; return value * 3; } Foo operator+(const Foo& other) { return Foo(value + other.value); }private: int value;};int main(){ Foo foo2(2); Foo foo3(30); Foo foo5 = foo2 + foo3; printf("foo5: %d\n", foo5); printf("foo5: %d\n", (int)foo5); return 0;}
2. 运行结果
foo5: 32int()foo5: 96
3. 分析
3.1 调用运算符重载的时机
通过运行结果,发现第一个print语句,并没有将Foo对象转化成int类型,即没有如预期的调用Foo的operator int(()函数;而第二个print语句因为加了强制类型转换,所以会调用operator int(),运行结果也是符合预期的。
现在分析为什么第一个print语句出现了异常。
3.2 显示规定类型转换
在此之前,我们先做另外的一些示例分析。新增一个函数,并验证:
void print(int n){printf("n: %d\n", n);}int main(){ Foo foo2(2); Foo foo3(30); Foo foo5 = foo2 + foo3; printf("foo5: %d\n", foo5); printf("foo5: %d\n", (int)foo5); print(foo5); return 0;}
运行结果:
foo5: 32int()foo5: 96int()n: 96
即因为print()的入参是int类型,所以print(foo5)的时候,会自动调用Foo.operator int()。
3.3 printf对变长参数的处理策略
通过这种对比,基本上确定是printf()函数在处理Foo对象的时候,并没有如预期调用Foo.operator int()。原因何在呢?因为printf没有类型检查,在格式化的时候,遇到了%d,它就把foo5变量看做整型变量,刚好该变量的地址处,可以解析出整数,即foo5对象的成员变量value的值。截图如下:
为了进一步验证,我们在Foo中添加其它一些数据成员。代码如下:
#include <cstdio>#include <iostream>class Foo {public: explicit Foo(int v): start(0x1234), value(v), end(0x5678) { } operator int() { std::cout << "int()" << std::endl; return value * 3; } Foo operator+(const Foo& other) { return Foo(value + other.value); }private: int start; int value; int end;};void print(int n){printf("n: %d\n", n);}int main(){ Foo foo2(2); Foo foo3(30); Foo foo5 = foo2 + foo3; printf("foo5: 0x%x\n", foo5); printf("foo5: %d\n", (int)foo5); print(foo5); return 0;}
运行结果:
foo5: 0x1234int()foo5: 96int()n: 96
这个运行结果可以验证之前的分析(或猜想)。
3.4 运算符重载无关的验证示例
进一步地,我们撇开运算符重载,来观察printf对变长参数的检查机制。增加一个结构体的定义,并把结构体对象用%d进行打印。示例代码如下:
struct Point3D { int x; int y; int z;};int main(){ Foo foo2(2); Foo foo3(30); Foo foo5 = foo2 + foo3; printf("foo5: 0x%x\n", foo5); printf("foo5: %d\n", (int)foo5); print(foo5); Point3D point = {3, 4, 5}; printf("point: %d\n", point); return 0;}
在VS2005下,编译无任何告警和错误。运行结果:
foo5: 0x1234int()foo5: 96int()n: 96point: 3
4. Linux GCC
4.1 编译器对比
为了和VS2005对比,我们在Linux环境上做对比。可以看到这里有编译告警,说明比VS2005的编译器做得更好。——在VS2008上,同样没有warning。
flying-bird@flyingbird:~/examples/cpp/operator_test$ g++ --versiong++ (Ubuntu 4.8.2-19ubuntu1) 4.8.2Copyright (C) 2013 Free Software Foundation, Inc.This is free software; see the source for copying conditions. There is NOwarranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.flying-bird@flyingbird:~/examples/cpp/operator_test$ g++ test.cctest.cc: In function ‘int main()’:test.cc:40:32: warning: format ‘%x’ expects argument of type ‘unsigned int’, but argument 2 has type ‘Foo’ [-Wformat=] printf("foo5: 0x%x\n", foo5); ^test.cc:45:32: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘Point3D’ [-Wformat=] printf("point: %d\n", point); ^flying-bird@flyingbird:~/examples/cpp/operator_test$ ./a.out foo5: 0x1234int()foo5: 96int()n: 96point: 3flying-bird@flyingbird:~/examples/cpp/operator_test$
4.2 不要忽视编译器告警
谈到printf,我们顺便看一个案例,这是曾经所在的一个项目组中出现过的。当时造成的故障是设备反复重启,为了定位该问题,团队曾经花了不少时间。
代码如下:
#include <stdio.h>int main(){ int error_code = 1; printf("error code: %s\n", error_code); return 0;}
这里简单的几行代码,是嵌入在当时所在项目(某个模块)数千行中的一部分。结合了本文的讨论,且代码只抽象出了这几行,所以还是容易看出问题所在。
编译、运行的情况:
flying-bird@flyingbird:~/examples/cpp/printf_warning$ gcc test.ctest.c: In function ‘main’:test.c:7:5: warning: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘int’ [-Wformat=] printf("error code: %s\n", error_code); ^flying-bird@flyingbird:~/examples/cpp/printf_warning$ ./a.out Segmentation faultflying-bird@flyingbird:~/examples/cpp/printf_warning$
运行这个小程序是段错误,当当时在设备上体现的就是反复重启。
另外可以看到,在Linux上面是有warning的,指出了错误所在。因为当初该代码在其他的代码中间,且遗留代码没有清空warning,然后新加入的代码也没有去关注warning,所以问题就这么流到后续环节。
这里体现的一个要点就是:千万别忽视编译告警。
作为对比,作者之前曾经经历过另外一个C++跨平台项目,当时的代码规模是5~6万行(NBNC)的规模,因为对代码质量非常关注,所以从项目一开始就构建了完善的CI环境,包括TDD、Code/Function Coverage等度量,另外还包括的一项就是实时清空warning。只要编译出现了warning,就会在CI中体现出现,自然也会在第一时间处理掉,低级问题想要漏到下一个环节是非常困难的。
当时这个项目(C++部分)有10人左右,实现的是真敏捷(包括很多公司没有实现的Pair Programming我们开展得也很好),每天每个人都是非常频繁地代码上库,且从未出现重大功能问题。
5. 其它
关于class对象的布局,可以参考《深入探索C++对象模型》,其第一章《关于对象》就有基本的介绍。
- C++运算符重载代码分析
- C++-运算符重载
- [C++]重载运算符
- C#:运算符重载
- c++-++运算符重载
- C++:重载运算符
- 运算符重载(C++)
- [c++]运算符重载
- 【C++】运算符重载
- C++--------------------------------------------运算符重载
- C#:运算符重载
- C++:运算符重载
- C++:运算符重载
- C#:运算符重载
- [C++]运算符重载
- 【c++】运算符重载
- C++:运算符重载
- C#:运算符重载
- ios周刊 第四期
- ckeditor和ckfinder结合使用时问题的解决办法
- 仿微信主界面导航栏图标字体颜色的变化
- 欢迎使用CSDN-markdown编辑器
- Elastic Search使用
- C++运算符重载代码分析
- Java Socket应用(三)——java中URL的应用
- pixhawk学习笔记---创建新的应用程序
- 最大流 ISAP
- dubbox+oceanus
- 随着ScrollView的滑动,渐渐的执行动画View
- 在html中“/”标识网站的根目录
- clang: error: linker command failed with exit code 1 (use -v to see invocation)
- windows系统使用word2vec