【问题标题】:C++ rely on implicit conversion to bool in conditions?C++ 在条件下依赖隐式转换为布尔值吗?
【发布时间】:2009-08-25 22:05:04
【问题描述】:

我在coding standards sheet 中发现了以下规则:

不要依赖条件中的隐式转换为布尔值。

if (ptr) // 错误

if (ptr != NULL) // 没问题

这条规则有多合理/有用?

编译后的代码有多少重载?

【问题讨论】:

    标签: c++ coding-style implicit-conversion


    【解决方案1】:

    从最严格的意义上说,您可以依赖隐式转换为 bool。向后兼容 C 需要它。

    因此它变成了代码可读性的问题。通常,代码标准的目的是强制与代码风格保持一致,无论您是否同意这种风格。如果您正在查看其他人的标准并想知道是否应该将其纳入自己的标准,请继续讨论它 - 但如果这是您公司的长期规则,请学会接受它。

    附:我刚加入了一个有同样规则的组织。我实际上对此很好,因为我一直认为显式优于隐式。我不能忍受的一件事是bool condition; ... if (condition == true),它太多余了,以至于我的眼睛都感到刺痛。

    无论检查是隐式还是显式,任何体面的编译器都应该生成相同的代码,因此不应该考虑。

    【讨论】:

    • 是的,你可以依赖隐式转换为bool,就像你可以依赖运算符优先级一样,即使编译器建议你放入多余的括号(对于这两种情况,它认为规则的概率为零将永远改变)。您甚至可以依靠隐式转换为 bool 来咬您,每当您忘记取消引用某个位置的指针时,由于某种原因(例如重载 bool 值也会有意义)。
    【解决方案2】:

    如果您想使用隐式转换为bool 的类,例如std::istream,那么该规则会让您陷入困境。此代码从文件中一次读取一个单词,直到到达 EOF:

    std::ifstream file("foo.txt");
    std::string word;
    
    while (file >> word)
    {
        // do stuff
    }
    

    流提取运算符返回对文件流的引用,该引用被隐式转换为bool,以指示流是否仍处于良好状态。当您到达文件末尾时,测试失败。您的编码标准使您无法使用这种通用结构。

    对于指针类型,这没什么大不了的。编译器可能会为隐式转换为bool 和针对NULL 的显式测试生成大致相同的代码。在这一点上,这是一个品味问题——从绝对意义上说,没有一个人“更好”。编码标准只是试图强制执行一致的样式。

    考虑到这一点,在处理内置类型(指针、整数等)时,您绝对应该遵循编码标准。如果您遇到与上述类似的情况,并且课程合法转换为 bool,我会向您的队友提出这个问题。

    【讨论】:

    • 应该注意的是,关于您所展示的结构是否是一个好主意存在一些争论。在重载运算符后面隐藏非类型转换功能属于“巧妙的技巧”类别,但它真的让事情更容易阅读吗?或者在您了解图书馆的技巧之前,它实际上是否会使它们更难阅读?与输入和输出流上的 >> 和
    • 在线常见问题解答 (parashift.com/c++-faq-lite/input-output.html#faq-15.4) 中有很常见的代码,因此优秀的 C++ 程序员需要能够理解它。然而,我确实同意,在奶牛回家之前,我们可以讨论它的优点。 :) 它为 OP 的编码标准提供了一个方便的反例。
    • 您仍然可以在遵守规则的情况下使用此功能:while(static_cast<bool>(file>>word))。在这一点上,风格指南作者可能想要改写规则:“不要依赖 default 在条件中从指针到 bool 的隐式转换”,这可能是最初基于给出的例子。或者他可能想要求static_cast 在那里,以提醒读​​者发生了转换。您无需使用隐式转换即可使用转换。
    • 这是一个有趣的案例。也许编码标准应该为隐式转换导致对转换运算符的调用添加一个例外,因为本质上具有这种转换运算符的类型旨在完全作为 bool 对象进行操作。 “b == true”(其中 b 是布尔值)没有意义,因此这在逻辑上适用于转换运算符的情况。
    • @onebyone:我查看了原始代码,我认为“这会从文件中读取单词”,因为我知道标准库(就像任何 C++ 程序员应该知道的那样;该结构是惯用的)。我看了你的代码,我认为你试图强迫它做一些它不想做的事情,这让我很害怕。如果我在现实生活中看到您的代码,我可能会花 10 分钟尝试验证它不是主要错误,并且一旦确定它没有损坏,就将其替换为 Kristo 的版本。
    【解决方案3】:

    在大多数情况下,这并不可怕,但如果您准确输入您的意思,它可以更具可读性。

    【讨论】:

    • 我无法立即找到参考资料,但我认为您所说的并不正确。我认为该标准将 NULL 定义为等于零。无论哪种方式,我都无法命名一个不是这种情况的系统......
    • NULL 在所有远程标准兼容系统上为 0(直到转换)。一些深奥的实现可能会将指针 0 编译为 0 以外的值,但在代码中它仍然是 0。
    • 我仍然没有给你的链接,但是来自我的 K&R 副本的 p102:“指针和整数不可互换。零是唯一的例外:常数零可以分配给“所以NULL当然应该在所有系统上都等于0。
    • 我的立场是正确的,语言掩盖了这个实现细节。也就是说,并非所有系统都在内部使用 0,但大多数系统都使用 0,因为它会通过导致内存错误来强制执行 null 取消引用错误。
    • 好吧,标准说“宏 NULL 是实现定义的 C++ 空指针常量”。此外,在指针转换中,“空指针常量是整数类型的整数常量表达式右值,其计算结果为零。”
    【解决方案4】:

    我想为这个问题添加一个历史观点。

    ANSI C(又名 C89/C90)是 C 的第一个正式规范。除了K&R C,您可能会注意到一些特殊的东西——没有布尔值的概念。控制流语句对 表达式 进行操作,它们的流程是根据表达式的计算结果是否为 0 来定义的。缺少布尔类型是 C 的最大疏忽之一。直到 C99,C 才获得_Bool 类型,并在stdbool.h 中为booltruefalse 定义了宏(注意它们仍然是整数)。同样,C++ 最初也没有布尔类型,在 C++98bool 中获得它。

    这就是为什么隐式转换为布尔值的原因。在我看来,继续依赖它是糟糕的设计——添加布尔值是有原因的。 true 应该始终等于 true,并且将所有非零值隐式转换为 true 是不令人满意的。

    NULL 也只是一个等于 0 的宏,所以具体来说,对于 C++11 中的指针,您应该使用 nullptr 来确定指针是否为空。

    if(ptr != nullptr)
    {
        // Do something with ptr.
    }
    

    【讨论】:

      【解决方案5】:

      有时我认为这条规则可以被打破,例如处理返回 int 而不是 bool 的标准库(为了与 C8​​9 兼容)。

      但是,此规则通常会导致代码更易于理解。它甚至在 C# 之类的语言中也得到了强制执行,并且在那里并没有太多抱怨,所以除了必须习惯它之外,遵循这条规则并没有什么大的缺点。

      【讨论】:

      • 我同意第二部分关于其他语言使用“强布尔”风格的观点,但我不同意在处理 C 风格的函数/库时打破规则的观点。就我个人而言,我认为任何返回 int 的东西都应该与 0 进行比较,以便让代码的读者清楚地知道发生了什么。例如if(someAwfulCStyleFunction() != 0) { /*error handling code*/ } 清楚地暗示“这个函数返回一个int 并且一个非零值代表一个错误”。
      【解决方案6】:

      这根本不会影响编译后的代码。

      至于它有多大用处 - 它肯定有助于理解来自 Java/C# 等语言的人。它会使您正在检查的内容更加明确。如果您开始将 int 与 NULL 进行比较,它将引发警告(从而表明您对所讨论变量的类型不清楚)。我个人更喜欢第一种形式,但这并不是一个完全不合理的要求。

      【讨论】:

        【解决方案7】:

        我非常不喜欢这条规则。在 C++ 中,对于指针类型(当然对于布尔类型)使用到 bool 的隐式转换是惯用的。 IMO,它更容易阅读

        bool conditionMet = false;
        
        while (!conditionMet) // read as "while condition is not met"
        {
            /* do something */
        }
        

        比读这个:

        bool conditionMet = false;
        
        while (conditionMet == false) // read as "while conditionMet is false"
        {
            /* do something */
        }
        

        指针也一样。此外,通过引入不必要的比较,您又引入了另一个错误输入的机会,并以分配而不是比较结束,这当然会产生不希望的结果。如果您使用整数作为布尔值,就像旧的 C 代码一样,我认为您还应该使用到布尔值的隐式转换。

        【讨论】:

        • 我认为对于 bool 类型,它不会强制执行任何操作(尽管编译器可能会或可能不会做)。对于指针类型,它有点灰色。对于其他任何事情,您都应该使用比较运算符。
        • 您将苹果与橙子进行比较。您的示例不使用隐式转换,仅使用冗余。
        【解决方案8】:

        增加零收益的规则是您可以有利可图地删除的规则。

        【讨论】:

        • 但也有好处,尤其是可读性。仅仅因为你不喜欢它并不意味着它没有它的好处。
        • 我既不喜欢也不讨厌,只是觉得它没有正当的理由存在。在可管理的编码标准中,您只能获得大约 40 或 50 条规则 - 不要浪费它们。
        • 我不清楚它不会增加好处。对于指针,它是微不足道的,但我已经处理了很多代码,人们一直在使用 int 执行此操作,当它必须更改时,这是一个该死的麻烦。
        • 这也增加了风险...如果您打算输入“if (ptr != NULL)”并且您不小心删除了感叹号并改为输入“if (ptr = NULL)”,您已经在你的程序中添加了一个相当讨厌的错误。如果你输入“if (ptr)”,你就不会冒这个风险。 (OTOH,无论如何您都应该收到编译器警告)
        • 然而,类似的意思是写“if (!ptr)”,但不小心错过了击键,导致“if (ptr)”。一个不同的错误,但同样令人讨厌,编译器无法发出警告。
        【解决方案9】:

        这是一个非常愚蠢的规则。

        if (ptr != NULL) // ok
        

        那为什么不呢

         if ((ptr != NULL)==true) 
        

        【讨论】:

        • 因为将布尔值与布尔值进行比较是愚蠢的。将指针与常量进行比较就没有那么简单了。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-12-26
        • 2016-07-10
        • 2018-01-03
        • 1970-01-01
        • 1970-01-01
        • 2011-02-26
        • 1970-01-01
        相关资源
        最近更新 更多