Я просматриваю какой-то код и нашел в нем несколько тернарных операторов. Этот код представляет собой библиотеку, которую мы используем, и она должна быть довольно быстрой.
Я думаю, экономим ли мы что-нибудь, кроме места там.
Каков ваш опыт?
Я просматриваю какой-то код и нашел в нем несколько тернарных операторов. Этот код представляет собой библиотеку, которую мы используем, и она должна быть довольно быстрой.
Я думаю, экономим ли мы что-нибудь, кроме места там.
Каков ваш опыт?
Тернарный оператор не должен отличаться по производительности от хорошо написанного эквивалентного оператора 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);
Если выражения 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
сначала подчеркивает ветвление, а то, что нужно сделать, является второстепенным, в то время как тернарный оператор делает акцент на том, что нужно сделать, а не на выборе значений, с которыми это нужно сделать.
В разных ситуациях любой из них может лучше отражать «естественный» взгляд программиста на код и облегчать его понимание, проверку и поддержку. Вы можете обнаружить, что выбираете одно вместо другого в зависимости от порядка, в котором вы учитываете эти факторы при написании кода. это с помощью ?
:
- это наименее разрушительный способ выразить это и продолжить свой "поток" кодирования.
new Image()
выбрасывает, то theAudioClip
будет delete
d, несмотря на то, что он неинициализирован. Вы можете исправить это: все необработанные указатели должны быть инициализированы до того, как конструктор первого члена данных (т.е. 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, компилятор знает, когда я сделаю что-то позже, что может/случайно изменить то, что я считал исправленным.
По сути, я говорю, что тернарный оператор важен для корректности константы, а правильность константы - отличная привычка:
Что ж...
Я сделал несколько тестов с 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() в условии.
Если вы беспокоитесь об этом с точки зрения производительности, я был бы очень удивлен, если бы между ними была какая-то разница.
С точки зрения внешнего вида это в основном зависит от личных предпочтений. Если условие короткое, а части true/false короткие, то тернарный оператор подойдет, но все, что длиннее, имеет тенденцию быть лучше в выражении if/else (на мой взгляд).
Вы предполагаете, что между ними должно быть различие, тогда как на самом деле существует ряд языков, которые отказываются от оператора "if-else" в пользу выражения "if-else" (в в этом случае у них может даже не быть тернарного оператора, который больше не нужен)
Представить:
x = if (t) a else b
В любом случае, тернарный оператор — это выражение в некоторых языках (C, C#, C++, Java и т. д.), в которых нет выражений if-else, и поэтому он выполняет особую роль< /em> там.