Doppia negazione nel codice C ++

Sono appena entrato in un progetto con una base di codice piuttosto enorme.

Mi occupo principalmente di C ++ e gran parte del codice che scrivono usa la doppia negazione per la loro logica booleana.

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

So che questi ragazzi sono programmatori intelligenti, è ovvio che non lo stanno facendo per caso.

Non sono esperto esperto di C ++, la mia unica ipotesi sul perché stanno facendo questo è che vogliono essere assolutamente positivi sul fatto che il valore valutato sia la reale rappresentazione booleana. Quindi lo annullano, quindi lo annullano nuovamente per riportarlo al suo effettivo valore booleano.

È corretto o mi sto perdendo qualcosa?

È un trucco da convertire in bool.

In realtà è un linguaggio molto utile in alcuni contesti. Prendi queste macro (esempio dal kernel di Linux). Per GCC, sono implementati come segue:

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

Perché devono farlo? L’ __builtin_expect di GCC considera i suoi parametri long e non bool , quindi deve esserci qualche forma di conversione. Dal momento che non sanno che cosa è quando scrivono quei macro, è più semplice usare semplicemente il !! idioma.

Potrebbero probabilmente fare la stessa cosa confrontando lo 0, ma a mio parere, in realtà è più semplice fare la doppia negazione, dal momento che è il più vicino a un cast-to-bool che ha C.

Questo codice può essere usato anche in C ++ … è una cosa a un minimo comune denominatore. Se ansible, fai ciò che funziona sia in C che in C ++.

I programmatori pensano che convertirà l’operando in bool, ma poiché gli operandi di && sono già implicitamente convertiti in bool, è completamente ridondante.

Sì, è corretto e non ti manca qualcosa. !! è una conversione in bool. Vedi questa domanda per ulteriori discussioni.

È una tecnica per evitare di scrivere (variabile! = 0) – cioè per convertire da qualsiasi tipo si tratta di un bool.

Il codice IMO come questo non ha spazio nei sistemi che devono essere mantenuti – perché non è un codice immediatamente leggibile (quindi la domanda in primo luogo).

Il codice deve essere leggibile – altrimenti si lascia un debito legato al tempo per il futuro – poiché richiede del tempo per capire qualcosa che è inutilmente complicato.

Fa un passo avanti un avvertimento del compilatore. Prova questo:

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

La riga ‘bar’ genera un valore di forzatura su bool ‘true’ o ‘false’ (avviso di prestazioni) su MSVC ++, ma la linea ‘baz’ si insinua.

È operatore! sovraccarico?
In caso contrario, probabilmente lo fanno per convertire la variabile in un bool senza produrre un avviso. Questo non è sicuramente un modo standard per fare le cose.

Gli sviluppatori legacy C non avevano alcun tipo booleano, quindi spesso #define TRUE 1 e #define FALSE 0 e quindi utilizzavano tipi di dati numerici arbitrari per i confronti booleani. Ora che abbiamo bool , molti compilatori emetteranno degli avvertimenti quando alcuni tipi di assegnazioni e confronti sono fatti usando una combinazione di tipi numerici e tipi booleani. Questi due usi finiranno per collidere quando si lavora con il codice legacy.

Per ovviare a questo problema, alcuni sviluppatori utilizzano la seguente id quadro booleana !num_value restituisce bool true se num_value == 0 ; false altrimenti. !!num_value restituisce bool false se num_value == 0 ; true altrimenti. La singola negazione è sufficiente per convertire num_value in bool ; tuttavia, la doppia negazione è necessaria per ripristinare il senso originale dell’espressione booleana.

Questo modello è noto come un idioma , cioè, comunemente usato da persone che hanno familiarità con la lingua. Pertanto, non lo vedo come un anti-pattern, tanto quanto vorrei static_cast(num_value) . Il cast potrebbe fornire i risultati corretti, ma alcuni compilatori emettono quindi un avvertimento sul rendimento, quindi devi ancora risolverlo.

L’altro modo per affrontare questo è quello di dire, (num_value != FALSE) . Anche a me va bene, ma tutto sumto !!num_value è molto meno dettagliato, può essere più chiaro e non confonde la seconda volta che lo vedi.

Come ha detto Marcin , potrebbe essere importante se il sovraccarico dell’operatore è in gioco. Altrimenti, in C / C ++ non importa se non stai facendo una delle seguenti cose:

  • confronto diretto con true (o in C qualcosa di simile a una macro TRUE ), che è quasi sempre una ctriggers idea. Per esempio:

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

  • vuoi semplicemente qualcosa convertito in un valore 0/1 rigoroso. In C ++ un incarico a un bool lo farà implicitamente (per quelle cose che sono implicitamente convertibili in bool ). In C o se hai a che fare con una variabile non bool, questo è un idioma che ho visto, ma preferisco la (some_variable != 0) me stesso.

Penso che nel contesto di una più ampia espressione booleana, tutto ciò semplicemente ingombra.

Se la variabile è di tipo di object, potrebbe avere un! definito dall’operatore ma senza cast per il bool (o peggio per un cast implicito per int con semantica diversa. Chiamando l’operatore! due volte si ottiene una conversione in bool che funziona anche in casi strani.

!! era usato per far fronte al C ++ originale che non aveva un tipo booleano (come nessuno dei due C).


Esempio di problema:

Dentro if(condition) , la condition deve valutare un tipo come double, int, void* , ecc., Ma non bool perché non esiste ancora.

Supponiamo che una class esistesse int256 (un intero a 256 bit) e che tutte le conversioni / cast di interi fossero sovraccariche.

 int256 x = foo(); if (x) ... 

Per verificare se x era “vero” o diverso da zero, if (x) converte x in un numero intero e poi valuta se tale int era diverso da zero. Un sovraccarico tipico di (int) x restituirebbe solo gli LSbit di x . if (x) stava quindi testando solo gli LSbit di x .

Ma il C ++ ha il ! operatore. Un overload !x tipicamente valuterà tutti i bit di x . Quindi per tornare alla logica non invertita if (!!x) è usato.

Ref Le versioni precedenti di C ++ usano l’operatore `int` di una class quando valuta la condizione in un’istruzione` if () `?

È corretto ma, in C, inutile qui – ‘se’ e ‘&&’ tratterebbero l’espressione allo stesso modo senza ‘!!’.

La ragione per farlo in C ++, suppongo, è che ‘&&’ potrebbe essere sovraccaricato. Ma allora, così potrebbe ‘!’, Quindi non garantisce veramente di ottenere un bool, senza guardare il codice per i tipi di variable e api.call . Forse qualcuno con più esperienza C ++ potrebbe spiegare; forse è inteso come una sorta di misura difensiva, non una garanzia.

Forse i programmatori stavano pensando qualcosa del genere …

!! myAnswer è booleano. Nel contesto, dovrebbe diventare booleano, ma mi piacerebbe solo bang bang cose per essere sicuro, perché una volta c’era un bug misterioso che mi ha morso, e bang bang, l’ho ucciso.

Questo può essere un esempio del trucco del doppio colpo , vedi The Safe Bool Idiom per maggiori dettagli. Qui riassumo la prima pagina dell’articolo.

In C ++ ci sono diversi modi per fornire test booleani per le classi.

Un modo ovvio è l’ operator bool conversione operator bool operatore.

 // operator bool version class Testable { bool ok_; public: explicit Testable(bool b=true):ok_(b) {} operator bool() const { // use bool conversion operator return ok_; } }; 

Possiamo testare la class,

 Testable test; if (test) std::cout << "Yes, test is working!\n"; else std::cout << "No, test is not working!\n"; 

Tuttavia, opereator bool è considerato non sicuro perché consente operazioni non senso come il test << 1; o int i=test .

Usando l' operator! è più sicuro perché evitiamo problemi di conversione o sovraccarico impliciti.

L'implementazione è banale,

 bool operator!() const { // use operator! return !ok_; } 

I due modi idiomatici per testare l'object Testable sono

  Testable test; if (!!test) std::cout << "Yes, test is working!\n"; if (!test2) { std::cout << "No, test2 is not working!\n"; 

La prima versione if (!!test) è ciò che alcuni chiamano il trucco del doppio-botto .