《C++编程调试秘笈》——2.3 处理类型的正确方式

简介:

本节书摘来自异步社区出版社《C++编程调试秘笈》一书中的第2章,第2.3节,作者: 【美】Vladimir Kushnir, 【德】Nicolai M. Josuttis,更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.3 处理类型的正确方式

C++编程调试秘笈
现在,如果我们创建一个Time类,在内部实现中隐藏了从什么时间开始,以及用什么时间单位(秒、毫秒等)进行测量等细节,上面这些杂七杂八的问题就可以轻松得以避免。这种方法的一个优点是如果我们错误地传递了其他日期数据,而不是传递了时间(现在用Time类型表示),编译器马上就能捕捉到这种错误。这种方法的另一个优点是,如果Time类当前是用毫秒实现的,并且以后为了提高精度用微秒表示,我们只需要编辑一个类,修改内部实现的细节,而不会影响其余的代码。

因此,我们怎样才能在编译时而不是在运行时捕捉类型错误呢?我们首先可以用一个单独的类表示每种类型的数据。我们用int表示整数,用double表示浮点数,用std::string表示文本,用Date表示日期,用Time表示时间,对于其他类型的数据也都用一个单独的类表示。但是,只采用这种做法仍然是不够的。假设我们有两个类Apple和Orange,并有一个期望接受一个Orange类型的参数的函数:
image

但是,我们可能不小心向它提供了Apple类型的对象:

image

在有些情况下,这样的代码可以通过编译,因为C++编译器试图向我们提供帮助。只要可能,它会把Apple平静地转换为Orange。这可能通过以下两种方式发生。

(1)如果Orange类具有一个只接受一个Apple类型的参数的构造函数。

(2)如果Apple类具有一个可以把它转换为Orange的操作符。

当Orange类具有下面这样的定义时,就会发生第一种情况:

image

它甚至可以像下面这样:

image

即使在最后这个例子中,构造函数看上去像是具有两个输入,但它也可以只用一个参数就可以被调用,因此它也可以隐式地把Apple转换为Orange。这个问题的解决方案是用关键字explicit声明这类构造函数。这种做法可以防止编译器执行自动(隐式)转换,这样我们就可以迫使程序员在期望接受Orange的地方必须使用Orange:

image

第二个例子需要对应地修改为:

image

另一种让编译器知道怎么把Apple转换为Orange的方法是提供一个转换操作符:

image

这个操作符在此处的出现是非同寻常的,说明程序员用一种明确的方式向编译器提供了一种把Apple转换为Orange的方法,它并不是什么错误。因此,对所有接受一个参数的构造函数用关键字explicit进行声明,这是值得推荐的做法。一般而言,隐式转换的所有可能性都是不好的思路。因此,如果想按照上面这个例子一样在Apple类中提供一种把Apple转换为Orange的方法,下面是一种更好的方法:
image

在这个例子中,为了把Apple转换为Orange,需要采用下面的方式:

image

另外还有一种方法可以混合不同的数据类型,即使用枚举(enum)。考虑下面这个例子:假设我们定义了下面这两个枚举,分别表示一周中的某天以及月份。
image

这些常量实际上都是整数(例如,C内置的int类型)。如果我们有一个期望接受一周中的某天作为参数的函数:

image

下面这个调用将会在不产生任何警告的情况下通过编译:

image

在运行时,我们能够采取的措施不多,因为JAN和MON都是与1相等的整数。捕捉这类缺陷的方法是不使用创建整数的“单纯功能”枚举,而是使用创建新类型的枚举:
image

在这种情况下,期望接受一周中的某天为参数的函数将被声明为:
image

像下面这样试图用一个Month值调用这个函数:

image

将会产生编译错误:

image

这正是我们在这个例子中期待产生的效果。

但是,这种方法具有一个消极因素。在这个例子中,用枚举创建整型常量时,我们可以编写如下的代码:

image

但是当我们使用枚举创建新类型时,如下面的写法:

image

就无法通过编译。因此,如果我们需要迭代枚举的值,可以像原来一样使用整数。

当然,任何规则都有例外,有时候程序员有理由编写像Variant这样的类,允许进行隐式类型转换,以满足特定的需要。但是,绝大多数时候应该完全避免隐式类型转换,这就允许我们充分利用编译器检查不同变量类型的功能,早期(即在编译时)捕捉潜在的错误。

现在,假设我们已经尽自己所能使用了类型安全。遗憾的是,除了bool和char类型之外,每种类型可能包含的值的数量都是天文数字,通常只有一小部分值是合理的。例如,如果我们使用double类型表示股票的价格,可以很合理地确定股票的价格将在0到10 000之间波动(唯一的例外是Berkshire Hathaway公司的股票,它

的主人Warren Buffet显然并不相信把股票价格保持在合理范围内是个好主意,因此他从不对股票进行除权,在本书写作之时这个股票的价格是每股10万美元)。但即使是Berkshire Hathaway这样的股票,它的价格仍然只使用了double类型的很小一部分,因为double的范围高达10308,并且还包含了完全不适合表示股票价格的负数。由于大多数类型只有一小部分值是合理的,因此总是存在一些只能在运行时才能诊断的错误。

事实上,C语言的大多数问题,例如指定了越界索引,或通过指针运算不恰当地访问内容,只能在运行时才能得到诊断。由于这个原因,本书的剩余部分主要专注于讨论捕捉运行时错误。

本章所讨论的在编译时诊断错误的规则如下。

禁止隐式类型转换:用关键字explicit声明一个接受1个参数的构造函数,并避免使用转换操作符。
用不同的类表示不同的数据类型。
不要使用枚举创建整型常量,而是用它们创建新类型。
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。

相关文章
|
6月前
|
存储 算法 安全
c++模板进阶操作——非类型模板参数、模板的特化以及模板的分离编译
在 C++ 中,仿函数(Functor)是指重载了函数调用运算符()的对象。仿函数可以像普通函数一样被调用,但它们实际上是对象,可以携带状态并具有更多功能。与普通函数相比,仿函数具有更强的灵活性和可扩展性。仿函数通常通过定义一个包含operator()的类来实现。public:// 重载函数调用运算符Add add;// 创建 Add 类的对象// 使用仿函数return 0;
243 0
|
安全 程序员 编译器
【实战经验】17个C++编程常见错误及其解决方案
想必不少程序员都有类似的经历:辛苦敲完项目代码,内心满是对作品品质的自信,然而当静态扫描工具登场时,却揭示出诸多隐藏的警告问题。为了让自己的编程之路更加顺畅,也为了持续精进技艺,我想借此机会汇总分享那些常被我们无意间忽视却又导致警告的编程小细节,以此作为对未来的自我警示和提升。
1370 93
|
存储 C++ UED
【实战指南】4步实现C++插件化编程,轻松实现功能定制与扩展
本文介绍了如何通过四步实现C++插件化编程,实现功能定制与扩展。主要内容包括引言、概述、需求分析、设计方案、详细设计、验证和总结。通过动态加载功能模块,实现软件的高度灵活性和可扩展性,支持快速定制和市场变化响应。具体步骤涉及配置文件构建、模块编译、动态库入口实现和主程序加载。验证部分展示了模块加载成功的日志和配置信息。总结中强调了插件化编程的优势及其在多个方面的应用。
1268 164
|
10月前
|
存储 缓存 C++
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
|
10月前
|
存储 机器学习/深度学习 编译器
【C++终极篇】C++11:编程新纪元的神秘力量揭秘
【C++终极篇】C++11:编程新纪元的神秘力量揭秘
|
10月前
|
存储 算法 C++
深入浅出 C++ STL:解锁高效编程的秘密武器
C++ 标准模板库(STL)是现代 C++ 的核心部分之一,为开发者提供了丰富的预定义数据结构和算法,极大地提升了编程效率和代码的可读性。理解和掌握 STL 对于 C++ 开发者来说至关重要。以下是对 STL 的详细介绍,涵盖其基础知识、发展历史、核心组件、重要性和学习方法。
|
10月前
|
存储 安全 算法
深入理解C++模板编程:从基础到进阶
在C++编程中,模板是实现泛型编程的关键工具。模板使得代码能够适用于不同的数据类型,极大地提升了代码复用性、灵活性和可维护性。本文将深入探讨模板编程的基础知识,包括函数模板和类模板的定义、使用、以及它们的实例化和匹配规则。
|
Ubuntu Linux Shell
C++ 之 perf+火焰图分析与调试
【11月更文挑战第6天】在遇到一些内存异常的时候,经常这部分的代码是很难去进行分析的,最近了解到Perf这个神器,这里也展开介绍一下如何使用Perf以及如何去画火焰图。
487 5
|
消息中间件 存储 安全
|
安全 程序员 编译器
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
206 11