Привлекательность тернарного оператора против оператора if

Я просматриваю некоторый код, и я нашел в нем несколько троичных операторов. Этот код-это библиотека, которую мы используем, и она должна быть довольно быстрой.

Я думаю, если мы сохраняем что-нибудь, кроме места там.

каков ваш опыт?

5 ответов


производительность

тернарный оператор не должен отличаться по производительности от хорошо написанного эквивалента if/else заявление... они вполне могут разрешаться одним и тем же представлением в абстрактном синтаксическом дереве, проходить те же оптимизации и т. д..

вещи, которые вы можете делать только с ? :

если вы инициализируете константу или ссылку или разрабатываете, какое значение использовать внутри списка инициализации члена, то if/else заявления не может быть использован, но ? : можно:

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

X::X() : n_(n > 0 ? 2 * n : 0) { }

факторинг для краткого кода

ключи причины использования ? : включить локализацию и избежать избыточного повторения других частей тех же операторов / вызовов функций, например:

if (condition)
    return x;
else
    return y;

... только предпочтительнее...

return condition ? x : y;

...по удобочитаемости, если иметь дело с очень неопытными программистами, или некоторые из терминов достаточно сложны, чтобы ? : структура теряется в шуме. В более сложных случаях как:

fn(condition1 ? t1 : f1, condition2 ? t2 : f2, condition3 ? t3 : f3);

эквивалентной 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...

это много дополнительных вызовов функций, которые компилятор может или не может оптимизировать.

не могут ли названные временные объекты улучшить чудовищность if/else выше?

если выражения t1, f1, t2 etc. слишком многословны для повторного ввода, создание именованных временных файлов может помочь, но затем:

  • чтобы получить соответствие производительности ? : возможно, вам придется использовать 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);
}

в версии условного оператора выше,1 проходит стандартное преобразование к double чтобы тип соответствовал 2.0, т. е. is(double) перегрузка вызывается даже для true/1 ситуации. The if/else оператор не запускает это конверсии:true/1 филиал звонки is(int).

вы не можете использовать выражения с общего типа void в условном операторе, тогда как они действительны в операторах под if/else.

акцент: значение-выбор до / после действия, требующего значений

Есть другой акцент:

Теги if/else заявление подчеркивает первый ветвления и что надо сделать вторичный, в то время как тернарный оператор подчеркивает, что нужно сделать над выбором значений для этого.

в разных ситуациях любой из них может лучше отражать "естественную" точку зрения программиста на код и облегчать его понимание, проверку и обслуживание. Вы можете выбрать один из них на основе порядка, в котором вы учитываете эти факторы при написании кода - если вы запустили "что-то делать" , то найдите вас может использовать одно из нескольких (или нескольких) значений для этого, ? : это наименее разрушительный способ выразить это и продолжить кодирование "потока".


хорошо...

Я сделал несколько тестов с 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);

полученный код ассемблера с gcc-O3 имел 35 инструкций.

эквивалентный код с if / else + промежуточными переменными имел 36. С вложенным if / else, используя тот факт, что 3 > 2 > 1, я получил 44. Я даже не пытался расширить это на отдельные вызовы функций.

теперь я не делал никакого анализа производительности, и я не делал проверку качества результата ассемблерный код, но в чем-то простом, как это, без циклов e.т. c. Думаю, чем короче, тем лучше.

похоже, что есть некоторое значение для троичных операторов в конце концов: -)

это только в том случае, если скорость кода абсолютно важна, конечно. Операторы If/else намного легче читать при вложенности, чем что-то вроде (c1)?(c2)?(c3)?(c4)?:1:2:3:4. И наличие огромных выражений в качестве аргументов функции-это не удовольствие.

также имейте в виду, что вложенные тройные выражения делают рефакторинг кода-или отладку, помещая кучу удобных printfs () в условие - намного сложнее.


единственной потенциальной выгодой для тернарных операторов над простыми операторами if, на мой взгляд, является их способность использоваться для инициализации, что особенно полезно для const:

Э. Г.

const int foo = (a > b ? b : a - 10);

делать это с блоком if/else невозможно без использования функции cal. Если у вас есть много случаев таких вещей const, вы можете обнаружить, что есть небольшая выгода от инициализации const должным образом над назначением с if/else. Измерьте его! Хотя, вероятно, даже не будет измеримым. Причина, по которой я склонен это делать, заключается в том, что, пометив его const, компилятор знает, когда я делаю что-то позже, что может/случайно изменить что-то, что я думал, было исправлено.

фактически я говорю, что тернарный оператор важен для const-правильности, а правильность const-отличная привычка:

  1. это экономит много вашего времени, позволяя компилятору помочь вам обнаружить ошибки, которые вы make
  2. это потенциально может позволить компилятору применять другие оптимизации

вы предполагаете, что есть должно быть различие между ними, когда на самом деле существует ряд языков, которые отказываются от утверждения "если-еще" в пользу выражения "если-еще" (в этом случае они могут даже не иметь тернарного оператора, который больше не нужен)

представьте себе:

x = if (t) a else b

в любом случае,тернарный оператор является выражением на некоторых языках (C,C#, C++, Java и т. д.), которые делают не есть выражения "if-else" и, таким образом, это выполняет определенную роль там.


Если вы беспокоитесь об этом с точки зрения производительности, я был бы очень удивлен, если бы между ними было что-то другое.

с точки зрения взгляда и чувства это в основном сводится к личным предпочтениям. Если условие короткое, а истинные / ложные части короткие, то тернарный оператор в порядке, но что-то длиннее, как правило, лучше в выражении if/else (на мой взгляд).