Красота тернарного оператора по сравнению с оператором if

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

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

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


person hummingBird    schedule 16.11.2010    source источник
comment
Если бы тернарный оператор был быстрее, чем операторы if (или наоборот), компиляторы определенно преобразовали бы один в другой. Поэтому у них не должно быть разных характеристик производительности (при условии, что вы делаете оба утверждения одинакового качества).   -  person Lasse Espeholt    schedule 16.11.2010
comment
Во всяком случае, это µ-оптимизация. Если сомневаетесь: эталон.   -  person Gordon    schedule 16.11.2010
comment
возможный дубликат Преимущества использования условного оператора ?: (тройного)   -  person nawfal    schedule 23.04.2013
comment
ага... натыкаешься на старый вопрос, чтобы продвинуть свой собственный? ты можешь оказаться забаненным, мой друг...   -  person hummingBird    schedule 01.07.2013
comment
nawfal: эта ссылка специально для C#, так что ИМХО не очень хороший дубликат   -  person Tony Delroy    schedule 26.11.2013
comment
Лучше бы спросили на одном языке, иначе ответить, скорее всего, не получится. Версия C++: stackoverflow.com/questions/3565368/ternary- оператор-если-иначе   -  person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 19.05.2016


Ответы (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...

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

Кроме того, ? позволяет вам выбрать объект, а затем использовать его член:

(f() ? a : b).fn(g() ? c : d).field_name);

Эквивалент if/else будет следующим:

if (f())
    if (g())
        x.fn(c.field_name);
    else
        x.fn(d.field_name);
else
    if (g())
        y.fn(c.field_name);
    else
        y.fn(d.field_name);

Разве именованные временные объекты не могут улучшить описанное выше чудовище 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);
}

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

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

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

Тут другой акцент:

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

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

person Tony Delroy    schedule 16.11.2010
comment
прямой эквивалент вызова этой функции без тернарных операторов должен был бы использовать промежуточные переменные и отдельные условные операторы. Пока их область видимости локальна для функции, компилятор должен иметь возможность их оптимизировать. Без них вам пришлось бы писать целую страницу кода, который было бы невозможно читать и отлаживать, хотя современные компиляторы, вероятно, неплохо справились бы с его оптимизацией. - person thkala; 16.11.2010
comment
@thkala: хорошее уточнение моего чрезмерно лаконичного создания временных файлов тоже проблематично, но гарантирует производительность, аналогичную тернарному оператору. Компиляторы, вероятно, избавятся по крайней мере от части дерева функций if/else, но с распределения регистров, ограничения глубины и т. д., и трудно сказать, будет ли это сделано полностью. - person Tony Delroy; 16.11.2010
comment
Прошу прощения здесь. Хотя вы правильно объясняете. Условный оператор можно использовать в выражении, где asif()...else() может не использоваться. Я использовал этот трюк, чтобы решить пункт 10 в книге Скотта Мейера «Более эффективный C++» другим способом, избегая использования auto_ptr‹› . Посмотрите: siddhusingh.blogspot.com - person siddhusingh; 25.06.2013
comment
@TonyD: я думаю, вы ввели код в заблуждение. Попробуйте переформатировать код в вашем редакторе. Утечек памяти нет. Все, что я делаю, это: я инициализирую указатель переменной-члена на основе значения другой переменной, и если нет, я присваиваю ему значение NULL. Если это правда, то я также сначала присваиваю ему значение NULL, а затем с помощью оператора запятой я вызываю новый оператор. Когда последнее значение присваивается значению, если вы используете оператор запятой. В случае любого исключения я перехватываю его с помощью catch(...) , очищаю всю выделенную память, а затем снова выбрасываю ее из самого конструктора. - person siddhusingh; 26.06.2013
comment
@TonyD: Пожалуйста, посмотрите на код ниже. Я думаю, что вы смущены моим подходом. То, что вы упомянули, верно, но не в контексте, который я написал. Код ниже должен вывести 10. #include ‹iostream› #include ‹string› using namespace std; int main(int argc, char ** argv, char * arge[]) try { cout ‹‹ Hello World! ‹‹ эндл; строка ул = а; интервал * р; int * a = (str != ) ? р = 0, новый интервал (10): 0; если (a == 0) { cout ‹‹ a = 0 ‹‹ endl; } else { cout ‹‹ a = ‹‹ *a ‹‹ endl; } вернуть 0; } catch(...) { cerr ‹‹ Неперехваченное исключение... ‹‹ endl; } - person siddhusingh; 29.06.2013
comment
@siddhusingh: извините - теперь заметил, что ваш список инициализаторов включает эти операторы, поэтому любой указатель передается обратно члену данных. Но если new Image() выбрасывает, то theAudioClip будет deleted, несмотря на то, что он неинициализирован. Вы можете исправить это: все необработанные указатели должны быть инициализированы до того, как конструктор первого члена данных (т.е. theName) может сгенерировать, если у вас есть try/catch вокруг конструктора, как в : theName((theImage = theAudioClip = nullptr, name)). Сначала лучше с элементами данных указателя, но все еще хрупким. - person Tony Delroy; 01.07.2013

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

E.g.

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

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

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

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

Что ж...

Я сделал несколько тестов с 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. Я даже не пытался разложить это на отдельные вызовы функций.

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

Похоже, тернарные операторы все-таки имеют некоторую ценность :-)

Конечно, только в том случае, если скорость кода имеет решающее значение. Операторы if/else намного легче читать, когда они вложены друг в друга, чем что-то вроде (c1)?(c2)?(c3)?(c4)?:1:2:3:4. А использовать огромные выражения в качестве аргументов функции не весело.

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

person thkala    schedule 16.11.2010
comment
Кстати, было интересно, что когда я попытался быть умнее компилятора в коде 3 › 2 › 1, это, как я и ожидал, эффектно дало обратный эффект. Вывод: никогда не пытайтесь перехитрить компилятор! - person thkala; 16.11.2010

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

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

person Sean    schedule 16.11.2010

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

Представить:

x = if (t) a else b

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

person Community    schedule 16.11.2010