C++运算符重载代码分析

2024-09-04 22:32

本文主要是介绍C++运算符重载代码分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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: 32
int()
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: 32
int()
foo5: 96
int()
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: 0x1234
int()
foo5: 96
int()
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: 0x1234
int()
foo5: 96
int()
n: 96
point: 3


4. Linux GCC

4.1 编译器对比

为了和VS2005对比,我们在Linux环境上做对比。可以看到这里有编译告警,说明比VS2005的编译器做得更好。——在VS2008上,同样没有warning。

flying-bird@flyingbird:~/examples/cpp/operator_test$ g++ --version
g++ (Ubuntu 4.8.2-19ubuntu1) 4.8.2
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
flying-bird@flyingbird:~/examples/cpp/operator_test$ g++ test.cc
test.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: 0x1234
int()
foo5: 96
int()
n: 96
point: 3
flying-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.c
test.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 fault
flying-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++运算符重载代码分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1137216

相关文章

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的

Java中调用数据库存储过程的示例代码

《Java中调用数据库存储过程的示例代码》本文介绍Java通过JDBC调用数据库存储过程的方法,涵盖参数类型、执行步骤及数据库差异,需注意异常处理与资源管理,以优化性能并实现复杂业务逻辑,感兴趣的朋友... 目录一、存储过程概述二、Java调用存储过程的基本javascript步骤三、Java调用存储过程示

Visual Studio 2022 编译C++20代码的图文步骤

《VisualStudio2022编译C++20代码的图文步骤》在VisualStudio中启用C++20import功能,需设置语言标准为ISOC++20,开启扫描源查找模块依赖及实验性标... 默认创建Visual Studio桌面控制台项目代码包含C++20的import方法。右键项目的属性:

MySQL中的表连接原理分析

《MySQL中的表连接原理分析》:本文主要介绍MySQL中的表连接原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、环境3、表连接原理【1】驱动表和被驱动表【2】内连接【3】外连接【4编程】嵌套循环连接【5】join buffer4、总结1、背景

c++中的set容器介绍及操作大全

《c++中的set容器介绍及操作大全》:本文主要介绍c++中的set容器介绍及操作大全,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录​​一、核心特性​​️ ​​二、基本操作​​​​1. 初始化与赋值​​​​2. 增删查操作​​​​3. 遍历方

解析C++11 static_assert及与Boost库的关联从入门到精通

《解析C++11static_assert及与Boost库的关联从入门到精通》static_assert是C++中强大的编译时验证工具,它能够在编译阶段拦截不符合预期的类型或值,增强代码的健壮性,通... 目录一、背景知识:传统断言方法的局限性1.1 assert宏1.2 #error指令1.3 第三方解决

C++11委托构造函数和继承构造函数的实现

《C++11委托构造函数和继承构造函数的实现》C++引入了委托构造函数和继承构造函数这两个重要的特性,本文主要介绍了C++11委托构造函数和继承构造函数的实现,具有一定的参考价值,感兴趣的可以了解一下... 目录引言一、委托构造函数1.1 委托构造函数的定义与作用1.2 委托构造函数的语法1.3 委托构造函

MySQL数据库的内嵌函数和联合查询实例代码

《MySQL数据库的内嵌函数和联合查询实例代码》联合查询是一种将多个查询结果组合在一起的方法,通常使用UNION、UNIONALL、INTERSECT和EXCEPT关键字,下面:本文主要介绍MyS... 目录一.数据库的内嵌函数1.1聚合函数COUNT([DISTINCT] expr)SUM([DISTIN