Это разумное использование троичного оператора?

Существуют ли какие-либо проблемы с пониманием / обслуживаемостью, возникающие в результате

inVar1 == 0 ? NULL : v.push_back(inVar1);
inVar2 == 0 ? NULL : v.push_back(inVar2);

и так далее.

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

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

16 ответов

Решение

Я думаю, что это сбивает с толку и намного сложнее читать, чем просто печатать;

if (inVar != 0)
  v.push_back(inVar);

Мне пришлось несколько раз просканировать ваш пример, чтобы выяснить, какой будет результат с какой-то определенностью. Я бы даже предпочел однострочный оператор if() {}, чем ваш пример - и я ненавижу однострочный оператор if:)

Тернарный оператор предназначен для возврата значения.

ИМО, это не должно изменять состояние, и возвращаемое значение должно использоваться.

В другом случае используйте операторы if. Если операторы предназначены для выполнения блоков кода.

Тройка - хорошая вещь, и я обычно продвигаю ее использование.

Однако то, что вы делаете здесь, бросает тень на доверие. Это короче, да, но это излишне сложно.

Я думаю, что этого следует избегать. Вы можете использовать 1-строчный оператор if на его месте.

if(inVar1 != 0) v.push_back(inVar1);

Использование вами троичного оператора ничего не дает, и вы ухудшаете читаемость кодов.

Поскольку троичный оператор возвращает значение, которое вы не используете, это нечетный код. Использование if гораздо понятнее в таком случае, как ваш.

Компиляторы в эти дни сделают так же быстро, как троичный оператор.

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

Я голосую за

if ( inVar != 0 )
{
   v.push_back( inVar );
}

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

Как сказано в комментариях, это не относится к C++. GCC, например, выдаст ошибку в этом коде:

error: `(&v)->std::vector<_Tp, _Alloc>::push_back [with _Tp = int, _Alloc =
std::allocator<int>](((const int&)((const int*)(&inVar1))))' has type `void' 
and is not a throw-expression

Тем не менее, это можно обойти путем приведения:

inVar1 == 0 ? (void)0 : v.push_back(inVar1);
inVar2 == 0 ? (void)0 : v.push_back(inVar2);

Но какой ценой? И с какой целью?

Это не похоже на использование тернарного оператора здесь более кратким, чем оператор if в этой ситуации:

inVar1 == 0 ? NULL : v.push_back(inVar1);
if(inVar1 != 0) v.push_back(inVar1);

Хотя на практике я согласен с мнением тех, кто не одобряет этот тип письма (при чтении вам необходимо проделать дополнительную работу для сканирования выражения на предмет его побочных эффектов), я бы хотел предложить

!inVar1 ?: v.push_back(inVar1);
!inVar2 ?: v.push_back(inVar2);

... если ты идешь в неизвестность, то это так. GCC позволяет x ?: y на месте x ? x : y,:-)

Я использую троичный оператор, когда мне нужно вызвать некоторую функцию с условными аргументами - в этом случае это лучше, чем if,

Для сравнения:

printf("%s while executing SQL: %s",
        is_sql_err() ? "Error" : "Warning", sql_msg());

с

if (is_sql_err())
    printf("Error while executing SQL: %s", sql_msg());
else
    printf("Warning while executing SQL: %s", sql_msg());

Я считаю, что первое более привлекательно. И это соответствует принципу СУХОЙ, в отличие от последнего - вам не нужно писать две почти одинаковые строки.

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

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

class foo
{
public:
    foo(std::vector<int> input);
private:
    int* array;
    unsigned int size;
};

foo:foo(std::vector<int> input):size(input.size()), array( (input.size()==0)?
        NULL : new int[input.size])
{
    //code to copy elements and do other start up goes here
}

Вот как я использую троичный оператор. Я не думаю, что это так запутанно, как некоторые люди, но я думаю, что нужно ограничить, насколько они используют это.

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

Например:

if (inVar1 != 0)
  v.push_back(inVar1);
if (inVar2 != 0)
  v.push_back(inVar2);

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

SomeType st;
if (inVar1 != 0)
  st = v.push_back(inVar1);
else if (inVar2 != 0)
  st = v.push_back(inVar2);
SomeFunc(st);

Но это больше, чтобы переварить такой простой кусок кода. Мое решение: определить другую функцию.

SomeType GetST(V v, int inVar1, int inVar2){
    if (inVar1 != 0)
      return v.push_back(inVar1);
    if (inVar2 != 0)
      return v.push_back(inVar2);        
}

//elsewhere
SomeFunc(GetST(V v, inVar1, inVar2));

В любом случае, суть в следующем: если у вас есть какая-то логика, которая слишком замучена для троичного типа, но загромождает ваш код, если он помещен в оператор if, поместите его где-нибудь еще!

Я думаю, что вам лучше обслужить правильную структуру. Я даже предпочитаю всегда иметь фигурные скобки со своими структурами if, если мне придется добавить строки позже к условному выполнению.

if (inVar != 0) {
    v.push_back(inVar);
}
inVar1 != 0 || v.push_back(inVar1);
inVar2 != 0 || v.push_back(inVar2);

Распространенный паттерн встречается в таких языках, как Perl.

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

Как уже упоминалось, он не короче или не более понятен, чем 1-й строчный оператор if. Тем не менее, это также больше не - и на самом деле это не так сложно уловить. Если вы знаете троичного оператора, то совершенно очевидно, что происходит.

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

var2 = inVar1 == 0 ? NULL : v.push_back(inVar1);

Тот факт, что троичный оператор всегда возвращает значение - IMO - не имеет значения. Конечно, нет необходимости использовать все возвращаемые значения... в конце концов, присваивание возвращает значение.

При этом я бы заменил его оператором if, если бы натолкнулся на него с ветвью NULL.

Но, если он заменил 3 строки if:

if (inVar == 0) {
   v.doThingOne(1);
} else {
   v.doThingTwo(2);
}

с:

invar1 == 0 ? v.doThingOne(1) : v.doThingTwo(2);

Я мог бы оставить это... в зависимости от моего настроения.;)

Правильное выражение if более читабельно, как уже упоминали другие. Кроме того, когда вы просматриваете свой код с помощью отладчика, вы не сможете легко увидеть, какая ветвь оператора if берется, когда все в одной строке или вы используете троичное выражение:

if (cond) doIt();

cond ? noop() : doIt();

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

if (cond) {
    doIt();
}
Другие вопросы по тегам