Подвійне заперечення в C ++


124

Я щойно потрапив на проект із досить величезною базою коду.

Я в основному маю справу з C ++, і багато написаного коду використовує подвійне заперечення для їх булевої логіки.

 if (!!variable && (!!api.lookup("some-string"))) {
       do_some_stuff();
 }                                   

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

Я не досвідчений експерт C ++, я можу лише здогадатися, чому вони роблять це, що вони хочуть зробити абсолютно позитивним, що значення, що оцінюється, є фактичним булевим представленням. Тож вони заперечують його, потім знову заперечують, щоб повернути його до фактичного булевого значення.

Це правильно, чи я щось пропускаю?


4
перевірити тут, вже запитували, Є !! безпечний спосіб перетворення на bool в C ++?
Özgür

Цю тему тут обговорювали .
Діма

Відповіді:


121

Це хитрість перетворити на bool.


19
Я думаю, що чітко викласти це з (bool) було б зрозуміліше, навіщо використовувати цей складний !!, тому що він менш вводить текст?
Baiyan Huang

27
Однак це безглуздо в C ++ або сучасному C, або там, де результат використовується лише в бульовому виразі (як у питанні). Це було корисно тому , коли у нас не було boolтипу, щоб допомогти уникнути зберігання , відмінних від значень 1і 0в логічних змінних.
Майк Сеймур

6
@lzprgmr: явна передача викликає "попередження про продуктивність" у MSVC. Використання !!або !=0вирішення проблеми, і серед двох я знаходжу колишній очищувач (оскільки він буде працювати на більшій кількості типів). Крім того, я погоджуюся, що немає жодної причини використовувати жоден у відповідному коді.
Яків Галка

6
@Noldorin, я думаю, що це покращує читабельність - якщо ви знаєте, що це означає, це просто, акуратно і логічно.
jwg

19
Поліпшується? Чортове пекло ... дай мені щось, що ти куриш.
Нолдорін

73

Насправді це дуже корисна ідіома в деяких контекстах. Візьміть ці макроси (приклад з ядра Linux). Для GCC вони реалізовані так:

#define likely(cond)   (__builtin_expect(!!(cond), 1))
#define unlikely(cond) (__builtin_expect(!!(cond), 0))

Чому вони повинні це робити? GCC __builtin_expectтрактує свої параметри як longні bool, тому необхідна певна форма перетворення. Оскільки вони не знають, що condтаке, коли вони пишуть ці макроси, найчастіше просто використовувати ці!! ідіоми.

Можливо, вони могли б зробити те ж саме, порівнюючи проти 0, але, на мою думку, подвійне заперечення насправді простіше зробити, оскільки це найближче до переливання в булі, яке має C.

Цей код можна використовувати і в C ++ ... це найнижчий-спільний знаменник. Якщо можливо, виконайте те, що працює і в C, і в C ++.


Я думаю, що це має багато сенсу, коли ти продумаєш це. Я не читав кожної відповіді, але, схоже, процес перетворення не вказаний. Якщо у нас є значення з висотою 2 біт і зі значенням, яке має лише один біт, у нас буде ненульове значення. Заперечення ненульового значення призводить до булевого перетворення (помилкове, якщо воно дорівнює нулю, істинне інакше). Тоді повторне заперечення призводить до булевих, що представляє оригінальну істину.
Джої Карсон

Оскільки ТАК не дозволить мені оновити коментар, я додам виправлення до своєї помилки. Заперечення цілісного значення призводить до булевого перетворення (хибне, якщо воно не є нульовим, істинно інакше).
Джої Карсон

51

Кодери думають, що він перетворить операнда в bool, але, оскільки операнди && вже неявно перетворені в bool, це абсолютно зайве.


14
Visual C ++ в деяких випадках дає змогу зменшити продуктивність без цього фокусу.
Кирило Васильович Лядвінський

1
Я б припустив, що було б краще просто відключити попередження, ніж обходити марні попередження в коді.
Руслан

Можливо, вони цього не усвідомлюють. Це має ідеальний сенс в контексті макросу, де ви можете працювати з інтегральними типами даних, про які ви не знаєте. Розглянемо об'єкти з перевантаженим оператором дужок, щоб повернути цілісне значення, що представляє бітове поле.
Джої Карсон

12

Це техніка уникання написання (змінної! = 0) - тобто перетворення з будь-якого типу в bool.

Кодексу IMO, як це, немає місця в системах, які потрібно підтримувати - тому що це не відразу читабельний код (звідси питання в першу чергу).

Код повинен бути розбірливим - інакше ви залишаєте часовий борг у спадок на майбутнє - оскільки потрібен час, щоб зрозуміти щось непотрібне.


8
Моє визначення хитрості - це те, що не кожен може зрозуміти в першому читанні. Щось, що потрібно з'ясувати, - це хитрість. Також жахливо, тому що! Оператор може бути перевантажений ...
Річард Харрісон

6
@ orlandu63: проста клавіатура: bool(expr)це робиться правильно, і всі розуміють наміри з першого погляду. !!(expr)це подвійне заперечення, яке випадково перетворюється на bool ... це не просто.
Адрієн Пліссон

12

Так це правильно, і ні, ви нічого не пропускаєте. !!є перетворенням на bool. Дивіться це питання для більшого обговорення.


9

Це бічні кроки попередження компілятора. Спробуйте це:

int _tmain(int argc, _TCHAR* argv[])
{
    int foo = 5;
    bool bar = foo;
    bool baz = !!foo;
    return 0;
}

Рядок 'bar' генерує «примушує значення bool 'true' або 'false' (попередження про продуктивність)" на MSVC ++, але рядок 'baz' проникає через штраф.


1
Найчастіше зустрічається в самому API Windows, який не знає про boolтип - все закодовано як 0або 1як int.
Марк Рансом

4

Є оператором! перевантажений?
Якщо ні, вони, ймовірно, роблять це для перетворення змінної в bool, не створюючи попередження. Це, безумовно, не стандартний спосіб робити речі.


4

Розробники Спадщина C не мали логічний тип, тому вони часто #define TRUE 1і #define FALSE 0потім використовуються довільні числові типи даних для булевих порівнянь. Тепер, коли ми маємо bool, багато компіляторів надсилають попередження, коли певні типи призначень та порівнянь здійснюються за допомогою суміші числових типів та булевих типів. Ці два звичаї згодом зіткнуться під час роботи зі застарілим кодом.

Щоб вирішити цю проблему, деякі розробники використовують таку булеву ідентичність: !num_valuereturn bool trueif 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це набагато менш багатослівно, може бути зрозумілішим і не бентежить вдруге, коли ви його бачите.


2

!! використовувався для справлення з оригінальним C ++, який не мав булевого типу (як і 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)тоді було лише тестування LSbitsx .

Але C ++ має !оператора. Перевантажений !xзазвичай оцінює всі біти x. Отже, щоб повернутися до неперевернутої логікиif (!!x) використовується.

Ref Чи використовували старіші версії C ++ оператор `int` класу при оцінці умови в операторі` if ()?


1

Як згадував Марцін , це може мати значення, чи буде перевантаження оператора. В іншому випадку для C / C ++ це не має значення, крім випадків, коли ви робите одну з наступних речей:

  • пряме порівняння true(або в C щось на кшталт TRUEмакросу), що майже завжди є поганою ідеєю. Наприклад:

    if (api.lookup("some-string") == true) {...}

  • ви просто хочете щось перетворити на суворе значення 0/1. У C ++ присвоєння boolзаповіту буде робити це неявно (для тих речей, які неявно можна конвертувати bool). У C або якщо ви маєте справу зі змінною, що не стосується bool, це ідіома, яку я бачив, але я віддаю перевагу самій (some_variable != 0)різноманітності.

Я думаю, що в контексті більшого булевого виразу це просто захаращує речі.


1

Якщо змінна має тип об'єкта, вона може мати! визначений оператором, але не присвоєний bool (або ще гірше неявний кидок для int з різною семантикою. Виклик оператора! двічі призводить до перетворення в bool, яке працює навіть у дивних випадках).


0

Це правильно, але, в С, безглуздо тут - "якщо" і "&&" трактували б вираз так само без "!!".

Я вважаю, що причиною цього в C ++ є те, що "&&" може бути перевантажено. Але тоді, так міг «!», Так що це НЕ реально гарантувати , що ви отримаєте логічне значення, що не дивлячись на код для типів variableі api.call. Можливо, хтось із більшим досвідом C ++ міг би пояснити; можливо, це мається на увазі як глибокий захист, а не гарантія.


Компілятор трактує значення однаково так само, якщо він використовується лише як операнд до ifабо &&, але використання !!може допомогти деяким компіляторам, якщо if (!!(number & mask))його замінять bit triggered = !!(number & mask); if (triggered); на деяких вбудованих компіляторах з бітовими типами, присвоєння напр., 256-бітного типу приведе до нуля. Без цього !!, очевидно безпечне перетворення (копіювання ifумови на змінну, а потім розгалуження) не буде безпечним.
supercat

0

Можливо, програмісти думали щось подібне ...

!! myAnswer булевий. У контексті це повинно стати бурхливим, але я просто люблю багати речі, щоб переконатися, бо колись був таємничий помилок, який мене покусав, і тріск, я вбив його.


0

Це може бути приклад трюку з подвійним ударом , див . Ідіома безпечного булінгу для більш докладної інформації. Тут я підсумовую першу сторінку статті.

У C ++ існує ряд способів надання булевих тестів для занять.

Очевидний спосіб - 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)- це те, що деякі називають трюк подвійного удару .


1
Оскільки C ++ 11, можна використовувати explicit operator boolдля запобігання неявного перетворення в інші цілісні типи.
Арне Фогель
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.