Существуют места, где я проверяю на допустимые указатели, прежде чем я выполню операцию с ними; эти проверки могут вкладываться довольно глубоко иногда.
Например, я имею
if (a)
{
if (a->b())
{
if (a->b()->c())
{
a->b()->c()->DoSomething();
}
}
}
Мне действительно не нравится вид этого. Существует ли способ превратить это во что-то более читаемое? Идеально,
if (a && a->b() && a->b()->c() )
{
...
}
было бы большим, но очевидно не будет работать.
РЕДАКТИРОВАНИЕ - nvm пример, который я поднял, ДЕЙСТВИТЕЛЬНО работает, как все указали. Я действительно проверял его, чтобы видеть, работает ли это, но была ошибка в моем коде в моем тесте. понятное дело!
Почему последнее не работает?
В C && - это оператор короткого замыкания, поэтому он оценивается слева направо, и если какое-либо из оценок ложно, оценка останавливается.
Фактически, вы могли бы написать:
a && a->b() && a->b()->c() && a->b()->c()->DoSomething();
Из других ответов вы видели, что использование && будет работать и приведет к короткому замыканию оценки при обнаружении нулевого указателя.
Непростой программист во мне любит избегать повторения вызовов методов для подобных тестов, поскольку это позволяет не беспокоиться о том, идемпотентны они или нет. Один из вариантов - это переписать так
A* a;
B* b;
C* c;
if ((a=a()) && (b=a->b()) && (c=b->c())) {
c->doSomething();
}
По общему признанию многословно и немного неуклюже, но, по крайней мере, вы знаете, что каждый метод вызывается только один раз.
Почему «явно не сработает»? Поскольку оператор &&
оценивает только правый член, если левый действителен, перезапись совершенно безопасна.
Поскольку вы уже получили прямой ответ на свой вопрос, я просто упомяну, что длинные цепочки вызовов, как у вас, имеют запах кода, и вы можете подумать о лучшем дизайне. Такой дизайн может в этом случае включать использование шаблона нулевого объекта, чтобы ваш вызов мог просто сводиться к:
a->CDoSomething();
Цепочка работает, но это не обязательно лучший ответ в общем случае, особенно потому, что он скрывает точку отказа. Вместо этого я бы предложил уплостить тесты, перевернув логику так, чтобы она завершалась при отказе.
if (!pa)
return Fail("No pa");
B* pb = pa->b();
if (!pb)
return Fail("No pb");
C* pc = b->c();
if (!pc)
return Fail("No pc");
pc->DoSomething();
То же самое, но плоский и легко читаемый. Кроме того, поскольку он сразу же обрабатывает случай отказа, он не переводится в else
, до написания которого вы, возможно, так и не доберетесь.
В этом примере я предположил, что вы не хотите просто молча терпеть неудачу, поэтому я добавил Fail
в качестве помощника, который регистрирует текст и возвращает false. Вы также можете просто выбросить исключение. На самом деле, если бы различные методы сигнализировали о своей неудаче, выбрасывая соответствующее исключение, а не возвращая null, то все это было бы ненужным. Если желателен молчаливый отказ, то уместен шаблон объекта null.
if (a && a->b() && a->b()->c()) { a->b()->c()->DoSomething(); }
C++ выполняет ленивую оценку, поэтому это будет работать. Сначала будет оценено a, и если оно равно 0, то все условие будет ложным, поэтому остальные части оцениваться не будут.
Это работает и для оператора ||
. Если вы напишете if (a || b)
, то b не будет оценено, если a истинно.
Второй будет работать, если вы хотите вызвать только DoSomething ()
.
Ваш второй пример действительно работает на C / C ++. Он замыкается при достижении первого значения FALSE.
Цитата из K&R 1 :
Выражения, связанные
&&
или||
, вычисляются слева правильно, и гарантируется, что оценка прекратится, как только станет известна правда или ложь.
Следовательно, последний пример будет работать идеально, как отметил WhirlWind .
1 Язык программирования C , второе издание, стр. 21.