三元运算符与if语句的漂亮

hum*_*ird 25 c++ if-statement ternary-operator

我正在浏览一些代码,我发现了一些三元运算符.这段代码是我们使用的库,它应该非常快.

我在想,除了那里的空间,我们还要保存任何东西.

你有什么经历?

Ton*_*roy 50

性能

三元运算符的性能不应与编写良好的等效if/ else语句有所区别......它们可能会在抽象语法树中解析为相同的表示形式,进行相同的优化等.

你只能做的事情?:

如果您正在初始化常量或引用,或者确定在成员初始化列表中使用哪个值,那么if/ elsestatements不能使用,但? :可以是:

const int x = f() ? 10 : 2;

X::X() : n_(n > 0 ? 2 * n : 0) { }
Run Code Online (Sandbox Code Playgroud)

保理简洁的代码

密钥使用的原因? :包括本地化,并避免冗余地重复相同的语句/函数调用的其他部分,例如:

if (condition)
    return x;
else
    return y;
Run Code Online (Sandbox Code Playgroud)

......只比......更可取

return condition ? x : y;
Run Code Online (Sandbox Code Playgroud)

......在可读性的基础上,如果处理非常缺乏经验的程序员,或者某些术语足够复杂,? :结构会在噪音中丢失.在更复杂的情况下,例如:

fn(condition1 ? t1 : f1, condition2 ? t2 : f2, condition3 ? t3 : f3);
Run Code Online (Sandbox Code Playgroud)

等价if/ else:

if (condition1)
    if (condition2)
        if (condition3)
            fn(t1, t2, t3);
        else
            fn(t1, t2, f3);
    else if (condition3)
            fn(t1, f2, t3);
        else
            fn(t1, f2, f3);
else
    if (condition2)
       ...etc...
Run Code Online (Sandbox Code Playgroud)

这是编译器可能会或可能不会优化的许多额外函数调用.

无法命名临时改善上面的if/else怪物?

如果表达式t1,f1,t2等太冗长重复输入,创建一个名为临时工可能会有帮助,但后来:

  • 要获得性能匹配,? :您可能需要使用std::move,除非将相同的临时值传递给&&名为的函数中的两个参数:那么您必须避免它.这更复杂,更容易出错.

  • c ? x : y然后评估 c,但不是 x y两者,这使得nullptr在使用它之前测试指针是安全的,同时提供一些回退值/行为.该代码仅获得实际选择的 x y中的任何一个的副作用.对于命名临时对象,您可能需要if/在其初始化内部else? :内部执行不需要的代码,或者执行代码的次数超出预期.

功能差异:统一结果类型

考虑:

void is(int) { std::cout << "int\n"; }
void is(double) { std::cout << "double\n"; }

void f(bool expr)
{
    is(expr ? 1 : 2.0);

    if (expr)
        is(1);
    else
        is(2.0);
}
Run Code Online (Sandbox Code Playgroud)

在上面的条件运算符版本中,1进行标准转换以double使类型匹配2.0,这意味着is(double)即使对于true/ 1situation 也会调用重载.该if/ else语句不触发这个转换:在true/ 1分支呼叫is(int).

您不能void在条件运算符中使用整体类型的表达式,而它们在if/ 下的语句中有效else.

重点:在需要值的行动之前/之后进行价值选择

有一个不同的侧重点:

一个if/ else声明强调分支第一,什么是必须做的是次要的,而三元运营商强调什么是所有的值,做它的选择要做.

在不同的情况下,要么可以更好地反映程序员对代码的"自然"观点,又要使其更容易理解,验证和维护.根据您在编写代码时考虑这些因素的顺序,您可能会发现自己选择了一个 - 如果您已经启动"做某事"然后发现您可能会使用几个(或几个)值中的一个来做它是,? :是最不具破坏性的表达方式,并继续你的编码"流程".

  • @siddhusingh:抱歉 - 现在注意到您的初始化列表正在嵌入这些语句,因此任何指针都会传递回数据成员.但是,如果`new Image()`抛出那么'theAudioClip`将被删除,尽管它是未初始化的.你可以解决这个问题:所有原始指针应该在第一个数据成员(即``name`)构造函数可能抛出之前初始化,假设你在构造函数周围有一个`try` /`catch`,就像`:theName((theImage = theAudioClip = nullptr,name))`.首先使用指针数据成员更好,但仍然很脆弱. (2认同)

thk*_*ala 8

好...

我用GCC和这个函数调用了一些测试:

add(argc, (argc > 1)?(argv[1][0] > 5)?50:10:1, (argc > 2)?(argv[2][0] > 5)?50:10:1, (argc > 3)?(argv[3][0] > 5)?50:10:1);
Run Code Online (Sandbox Code Playgroud)

生成的汇编代码与gcc -O3有35条指令.

使用if/else +中间变量的等效代码有36.使用嵌套的if/else使用3> 2> 1的事实,我得到44.我甚至没有尝试将其扩展为单独的函数调用.

现在我没有进行任何性能分析,也没有对生成的汇编程序代码进行质量检查,但是在没有循环的情况下这样简单的事情我认为更短更好.

看来三元运营商毕竟有一些价值:-)

当然,只有当代码速度绝对至关重要时.如果/ else语句在嵌套时比(c1)更容易读取?(c2)?(c3)?(c4)?:1:2:3:4.拥有巨大的表达式作为函数参数并不好玩.

还要记住,嵌套的三元表达式可以重构代码 - 或者通过在一个条件下放置一堆方便的printfs()来进行调试 - 要困难得多.

  • 顺便说一句,有趣的是,当我试图比3> 2> 1代码中的编译器更聪明时,它正如我所预料的那样引人注目地适得其反.结论:永远不要试图超越编译器! (2认同)

Fle*_*exo 7

在我看来,三元运算符对普通if语句的唯一潜在好处是它们能够用于初始化,这对以下内容特别有用const:

例如

const int foo = (a > b ? b : a - 10);
Run Code Online (Sandbox Code Playgroud)

如果不使用函数cal,则无法使用if/else块执行此操作.如果你碰巧有很多像这样的const事件,你可能会发现使用if/else正确地初始化const有一点小小的好处.测量它!可能甚至不会被测量.我倾向于这样做的原因是因为通过标记它const,编译器知道我什么时候做某事可能/会意外地改变我认为已修复的东西.

实际上,我所说的是三元运算符对于const正确性很重要,并且const正确性是一个很好的习惯:

  1. 通过让编译器帮助您发现错误,可以节省大量时间
  2. 这可能会让编译器应用其他优化


Sea*_*ean 5

如果您从性能角度担心它,那么如果两者之间有任何不同,我会感到非常惊讶。

从外观和感觉的角度来看,这主要取决于个人喜好。如果条件很短并且真/假部分很短,那么三元运算符就可以了,但在 if/else 语句中任何更长的内容往往会更好(在我看来)。