Ответ 1
Это трюк для преобразования в bool.
Я только что пришел в проект с довольно большой базой кода.
В основном я занимаюсь С++, и много кода, который они пишут, использует двойное отрицание для своей логической логики.
if (!!variable && (!!api.lookup("some-string"))) {
do_some_stuff();
}
Я знаю, что эти ребята - умные программисты, очевидно, что они не делают этого случайно.
Я не опытный эксперт на С++, я только предполагаю, почему они делают это, так это то, что они хотят сделать абсолютно уверенным, что оцениваемое значение является фактическим булевым представлением. Поэтому они отрицают это, а затем отрицают это снова, чтобы вернуть его к фактической логической ценности.
Является ли это правильным, или я что-то не хватает?
Это трюк для преобразования в bool.
На самом деле это очень полезная идиома в некоторых контекстах. Возьмите эти макросы (пример из ядра Linux). Для GCC они реализованы следующим образом:
#define likely(cond) (__builtin_expect(!!(cond), 1))
#define unlikely(cond) (__builtin_expect(!!(cond), 0))
Почему они должны это делать? GCC __builtin_expect
обрабатывает свои параметры как long
, а не bool
, поэтому должна быть какая-то форма преобразования. Поскольку они не знают, что такое cond
, когда они пишут эти макросы, наиболее просто использовать !!
idiom.
Они, вероятно, могли бы сделать то же самое, сравнивая с 0, но, на мой взгляд, на самом деле более просто сделать двойное отрицание, так как это самое близкое к cast-to-bool, которое имеет C.
Этот код может быть использован и на С++... это вещь с наименьшим общим знаменателем. Если возможно, выполните то, что работает как на C, так и на С++.
Кодеры считают, что он преобразует операнд в bool, но поскольку операнды && уже неявно преобразуются в bool, это совершенно избыточно.
Да, это правильно, и вы ничего не пропустили. !!
- это преобразование в bool. Подробнее см. этот вопрос.
Это метод, чтобы избежать записи (variable!= 0) - то есть для преобразования из любого типа, который он относится к bool.
ИМО-код, подобный этому, не имеет места в системах, которые необходимо поддерживать, потому что это неточно читаемый код (отсюда и вопрос в первую очередь).
Код должен быть разборчивым - иначе вы оставите долговременное долговое наследие на будущее - так как требуется время, чтобы понять то, что бесполезно запутано.
Это побочные действия для предупреждения компилятора. Попробуйте следующее:
int _tmain(int argc, _TCHAR* argv[])
{
int foo = 5;
bool bar = foo;
bool baz = !!foo;
return 0;
}
Строка "bar" генерирует "принудительное значение для bool" true "или" false "(предупреждение о производительности)" на MSVС++, но строка "baz" проходит через штраф.
Является ли оператор! перегружен?
Если нет, они, вероятно, делают это, чтобы преобразовать переменную в bool без предупреждения. Это определенно не стандартный способ делать вещи.
У разработчиков Legacy C не было Boolean-типа, поэтому они часто #define TRUE 1
и #define FALSE 0
, а затем использовали произвольные числовые типы данных для булевых сравнений. Теперь, когда у нас есть bool
, многие компиляторы будут выдавать предупреждения, когда определенные типы присвоений и сравнений производятся с использованием комбинации числовых типов и булевых типов. Эти два использования в конечном итоге столкнутся при работе с устаревшим кодом.
Чтобы обойти эту проблему, некоторые разработчики используют следующий логический идентификатор: !num_value
возвращает bool true
, если num_value == 0
; false
в противном случае. !!num_value
возвращает bool false
, если num_value == 0
; true
в противном случае. Единственного отрицания достаточно для преобразования num_value
в bool
; однако двойное отрицание необходимо для восстановления исходного смысла булевого выражения.
Этот шаблон известен как идиома, то есть что-то, обычно используемое людьми, знакомыми с языком. Поэтому я не вижу его как анти-шаблон, насколько я бы хотел static_cast<bool>(num_value)
. Листинг может очень хорошо дать правильные результаты, но некоторые компиляторы затем выдают предупреждение о производительности, поэтому вам все равно придется это решить.
Другим способом решения этого является, например, (num_value != FALSE)
. Я тоже в порядке, но в целом, !!num_value
гораздо менее подробный, может быть более ясным и не путает второй раз, когда вы его видите.
Как упоминалось Marcin, вполне возможно, что в игре будет перегрузка оператора. В противном случае в C/С++ это не имеет значения, если вы выполняете одно из следующих действий:
прямое сравнение с true
(или в C чем-то вроде макроса true
), что почти всегда плохое. Например:
if (api.lookup("some-string") == true) {...}
вы просто хотите, чтобы что-то конвертировалось в строгое значение 0/1. В С++ присваивание bool
будет делать это неявно (для тех вещей, которые неявно конвертируются в bool
). В C или если вы имеете дело с переменной non-bool, это идиома, которую я видел, но я предпочитаю сорт (some_variable != 0)
сам.
Я думаю, что в контексте более крупного булевского выражения он просто загромождает вещи.
Если переменная имеет тип объекта, она может иметь значение! оператор, но не сбрасывается в bool (или, что еще хуже, неявный приведение к int с другой семантикой. Вызов оператора! дважды приводит к преобразованию в bool, который работает даже в странных случаях.
!!
использовался, чтобы справиться с оригинальным С++, который не имел логического типа (как и C).
Пример задачи:
Внутри if(condition)
значение condition
должно оцениваться для некоторого типа типа double, int, void*
и т.д., но не bool
, поскольку он еще не существует.
Скажем, что существовал класс int256
(целое число 256 бит), и все целые преобразования/трансляции были перегружены.
int256 x = foo();
if (x) ...
Чтобы проверить, был ли x
"истинным" или ненулевым, if (x)
преобразует x
в какое-то целое число, а затем оценит, был ли этот int
отличным от нуля. Типичная перегрузка (int) x
вернет только LSbits x
. if (x)
тогда тестировал только LSbits x
.
Но С++ имеет оператор !
. Перегруженный !x
обычно оценивает все биты x
. Итак, чтобы вернуться к неинвертированной логике, используется if (!!x)
.
Ref Использовали ли более старые версии С++ оператор` int` класса при оценке условия в операторе `if()`?
Это правильно, но в C здесь бессмысленно - "if" и "& &" будет рассматривать выражение таким же образом без "!!".
Поводом для этого в С++, я полагаю, является то, что '& &' может быть перегружена. Но тогда, так можно "!", Поэтому на самом деле это не гарантирует, что вы получите bool, не глядя на код для типов variable
и api.call
. Может быть, кто-то, у кого больше опыта на C++, может объяснить; возможно, это означало в качестве меры защиты в глубину, а не гарантии.
Возможно, программисты думали что-то вроде этого...
!! myAnswer является логическим. В контексте он должен стать логическим, но мне просто нравится бить вещи, чтобы убедиться, потому что когда-то была загадочная ошибка, которая укусила меня, и ударил, я убил ее.
Это может быть пример трюка double-bang, см. The Safe Bool Idiom для получения более подробной информации. Здесь я резюмирую первую страницу статьи.
В С++ существует несколько способов предоставления булевых тестов для классов.
Очевидным способом является оператор преобразования
operator bool
.
// operator bool version
class Testable {
bool ok_;
public:
explicit Testable(bool b=true):ok_(b) {}
operator bool() const { // use bool conversion operator
return ok_;
}
};
Мы можем проверить класс,
Testable test;
if (test)
std::cout << "Yes, test is working!\n";
else
std::cout << "No, test is not working!\n";
Однако opereator bool
считается небезопасным, поскольку он допускает бессмысленные операции, такие как test << 1;
или int i=test
.
Использование
operator!
безопаснее, потому что мы избегаем проблем с неявным преобразованием или перегрузкой.
Реализация тривиальна,
bool operator!() const { // use operator!
return !ok_;
}
Два идиоматических способа тестирования объекта Testable
:
Testable test;
if (!!test)
std::cout << "Yes, test is working!\n";
if (!test2) {
std::cout << "No, test2 is not working!\n";
Первая версия if (!!test)
- это то, что некоторые люди называют трюком double-bang.