Присвоение переменной в условии "если"

Недавно я просто потерял время на выяснение ошибки в моем коде, которая была вызвана опечаткой:

if(a=b)

вместо:

if(a==b)

Мне было интересно, если есть какой-то конкретный случай, вы хотели бы присвоить значение переменной в if утверждение или, если нет, почему компилятор не выдает предупреждение или ошибку?

9 ответов

Решение
if (Derived* derived = dynamic_cast<Derived*>(base)) {
   // do stuff with `derived`
}

Хотя это часто упоминается как анти-шаблон ("используйте виртуальную рассылку!"), Иногда Derived тип имеет функциональность, что Base просто нет (и, следовательно, отдельные функции), и это хороший способ включить это семантическое различие.

Вот немного истории о синтаксисе, о котором идет речь.

В классическом C обработка ошибок часто выполнялась путем написания чего-то вроде:

int error;
...
if(error = foo()) {
    printf("An error occured: %s\nBailing out.\n", strerror(error));
    abort();
}

Или всякий раз, когда был вызов функции, который мог бы вернуть нулевой указатель, идиома использовалась наоборот:

Bar* myBar;
... //in old C variables had to be declared at the start of the scope
if(myBar = getBar()) {
    //do something with myBar
}

Однако этот синтаксис опасно близок к

if(myValue == bar()) ...

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

if((myBar = getBar())) {  //tells the compiler: Yes, I really want to do that assignment!

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

Bar* myBar = getBar();
if(myBar) {

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

if(Bar* myBar = getBar()) {

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

if(Bar* myBar = getBar()) {
    ...
}
foo(myBar->baz);  //compiler error
//or, for the C++ enthusiasts:
myBar->foo();     //compiler error

Без определения переменной внутри if Заявление, это условие не будет обнаружено.

Короче говоря, длинный ответ: Синтаксис в вашем вопросе является продуктом простоты и силы старого C, но это зло, поэтому компиляторы могут предупредить об этом. Поскольку это также очень полезный способ выражения общей проблемы, теперь существует очень лаконичный и надежный способ добиться того же поведения. И есть много хороших, возможных применений для этого.

Оператор присваивания возвращает значение присвоенного значения. Итак, я мог бы использовать его в такой ситуации:

if(x = getMyNumber())

Тогда я назначаю x быть значением, возвращаемым getMyNumber и я проверяю, не ноль ли это.

Избегайте этого, я привел вам пример, чтобы помочь вам понять это.

Редактировать: добавление Просто предложение (может понравиться).

Чтобы избежать таких ошибок, вплоть до некоторых расширений, следует написать, если условие как if(NULL == ptr) вместо if(ptr == NULL) Потому что, когда вы неправильно пишете оператор проверки равенства == как оператор =, компиляция выдаст ошибку lvalue с if(NULL = ptr), но if(res = NULL) передается компилятором (что вы не имеете в виду) и остается ошибкой в ​​коде во время выполнения.
По той же причине я предпочитаю писать if(getMyNumber() ==x) вместо if(x == getMyNumber())

Следует также прочитать: Критика в отношении такого рода кода.

почему компилятор не выдает предупреждение

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

Например, в Visual C++ необходимо включить C4706 (или предупреждения уровня 4 в целом). Я обычно включаю как можно больше предупреждений и делаю код более явным, чтобы избежать ложных срабатываний. Например, если я действительно хотел сделать это:

if (x = Foo()) { ... }

Тогда я бы написал это так:

if ((x = Foo()) != 0) { ... }

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

Единственным недостатком этого подхода является то, что вы не можете использовать его, когда переменная объявлена ​​в условии. То есть вы не можете переписать:

if (int x = Foo()) { ... }

как

if ((int x = Foo()) != 0) { ... }

Синтаксически это не работает. Таким образом, вы должны либо отключить предупреждение, либо пойти на компромисс в отношении того, насколько сильно вы охватываете x,

В С ++17 можно использовать:

      if (<initialize> ; <conditional_expression>) { <body> }

Аналогичен инициализатору итератора цикла for.

Вот пример:

      if (Employee employee = GetEmployee(); employee.salary > 100) { ... }

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

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

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

std::string e;
if( myMap[e = "ab"].isNotValid() ||
    myMap[e = "cd"].isNotValid() ||
    myMap[e = "ef"].isNotValid() )
{
    // here, e has the key for which the validation failed
}

Так что, если второе условие является тем, которое оценивается как истинное, e будет равно "cd". Это связано с поведением короткого замыкания || который предписан стандартом (если не перегружен). Смотрите этот ответ для более подробной информации о коротком замыкании.

Делая назначение в if это довольно распространенная вещь, хотя также часто люди делают это случайно.

Обычный шаблон:

if (int x = expensive_function_call())
{
  // ...do things with x
}

Анти-паттерн - это то, где вы ошибочно назначаете вещи:

if (x = 1)
{
  // Always true
}
else
{
  // Never happens
}

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

if (1 = x)
{
  // Compiler error, can't assign to 1
}

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

довольно распространенный случай. использовать

      if (0 == exit_value)

вместо

      if (exit_value == 0)

такая опечатка вызовет ошибку компиляции

Другие вопросы по тегам