Doppia negazione in C ++


124

Sono appena arrivato a un progetto con una base di codice piuttosto enorme.

Mi occupo principalmente di C ++ e gran parte del codice che scrivono utilizza 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 fanno per caso.

Non sono un esperto esperto di C ++, la mia unica ipotesi sul motivo per cui lo stanno facendo è che vogliano rendere assolutamente positivo che il valore valutato è la rappresentazione booleana effettiva. Quindi lo negano, quindi lo negano di nuovo per riportarlo al suo valore booleano effettivo.

È corretto o mi manca qualcosa?



Questo argomento è stato discusso qui .
Dima

Risposte:


121

È un trucco per convertire in bool.


19
Penso che lanciarlo esplicitamente con (bool) sarebbe più chiaro, perché usare questo difficile !!, perché è di meno battitura?
Baiyan Huang

27
Tuttavia, è inutile in C ++ o C moderno, o dove il risultato viene utilizzato solo in un'espressione booleana (come nella domanda). Era utile quando non avevamo il booltipo, per evitare di memorizzare valori diversi da 1e 0nelle variabili booleane.
Mike Seymour

6
@lzprgmr: il cast esplicito provoca un "avviso di prestazione" su MSVC. Utilizzo !!o !=0risolve il problema, e tra i due trovo il primo pulitore (visto che funzionerà su una maggior quantità di tipi). Inoltre sono d'accordo sul fatto che non c'è motivo di utilizzare nessuno dei due nel codice in questione.
Yakov Galka

6
@Noldorin, penso che migliori la leggibilità: se sai cosa significa, è semplice, chiaro e logico.
jwg

19
Migliora? Maledizione ... dammi un po 'di quello che stai fumando.
Noldorin

73

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

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

Perché devono farlo? GCC __builtin_expecttratta i suoi parametri come longe non bool, quindi deve esserci una qualche forma di conversione. Dal momento che non sanno cosa condsia quando scrivono quelle macro, è più generale usare semplicemente l' !!idioma.

Probabilmente potrebbero fare la stessa cosa confrontando con 0, ma secondo me, in realtà è più semplice eseguire la doppia negazione, poiché è il più vicino a un cast-to-bool che C ha.

Questo codice può essere utilizzato anche in C ++ ... è una cosa con il minimo comune denominatore. Se possibile, fai ciò che funziona sia in C che in C ++.


Penso che questo abbia molto senso se ci pensi. Non ho letto tutte le risposte, ma sembra che il processo di conversione non sia specificato. Se abbiamo un valore con 2 bit alti e con un valore che ha solo un bit alto, avremo un valore diverso da zero. La negazione di un valore diverso da zero risulta in una conversione booleana (false se è zero, true altrimenti). Quindi negando di nuovo si ottiene un valore booleano che rappresenta la verità originale.
Joey Carson

Poiché SO non mi consentirà di aggiornare il mio commento, aggiungerò una correzione al mio errore. La negazione di un valore integrale produce una conversione booleana (false se è diverso da zero, vero altrimenti).
Joey Carson

51

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


14
Visual C ++ offre prestazioni in calo in alcuni casi senza questo trucco.
Kirill V. Lyadvinsky

1
Suppongo che sarebbe meglio disabilitare solo l'avviso piuttosto che aggirare gli avvisi inutili nel codice.
Ruslan,

Forse non se ne rendono conto. Tuttavia, questo ha perfettamente senso nel contesto di una macro, dove potresti lavorare con tipi di dati integrali di cui non sei a conoscenza. Considera gli oggetti con l'operatore di parentesi sovraccaricato per restituire un valore integrale che rappresenta un campo di bit.
Joey Carson

12

È una tecnica per evitare di scrivere (variabile! = 0) - cioè per convertire da qualunque tipo sia in un bool.

Il codice IMO come questo non ha posto in sistemi che devono essere mantenuti, perché non è un codice immediatamente leggibile (da qui la domanda in primo luogo).

Il codice deve essere leggibile - altrimenti lasci un'eredità di debito di tempo per il futuro - poiché ci vuole tempo per capire qualcosa che è inutilmente contorto.


8
La mia definizione di trucco è qualcosa che non tutti possono capire alla prima lettura. Qualcosa che deve essere scoperto è un trucco. Anche orribile perché il! l'operatore potrebbe essere sovraccarico ...
Richard Harrison

6
@ orlandu63: il typecasting semplice è bool(expr): fa la cosa giusta e tutti ne capiscono l'intento a prima vista. !!(expr)è una doppia negazione, che si converte accidentalmente in bool ... non è semplice.
Adrien Plisson

12

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


9

Fa un passo laterale un avviso 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 a boolizzare" true "o" false "(avviso di prestazioni)" su MSVC ++, ma la riga "baz" si insinua bene.


1
Più comunemente riscontrato nella stessa API di Windows che non conosce il booltipo: tutto è codificato come 0o 1in un file int.
Mark Ransom

4

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


4

Gli sviluppatori Legacy C non avevano tipo booleano, in modo che spesso #define TRUE 1e #define FALSE 0quindi utilizzati i tipi di dati numerici arbitrari per i confronti booleani. Ora che abbiamo bool, molti compilatori emetteranno avvisi quando determinati tipi di assegnazioni e confronti vengono effettuati utilizzando una combinazione di tipi numerici e tipi booleani. Questi due utilizzi finiranno per entrare in conflitto quando si lavora con il codice legacy.

Per aggirare questo problema, alcuni sviluppatori utilizzano la seguente identità booleana: !num_valuerestituisce bool trueif num_value == 0; falsealtrimenti. !!num_valuerestituisce bool falseif num_value == 0; truealtrimenti. La singola negazione è sufficiente per convertire num_valuein bool; tuttavia, la doppia negazione è necessaria per ripristinare il senso originale dell'espressione booleana.

Questo modello è noto come idioma , cioè qualcosa di comunemente usato da persone che hanno familiarità con la lingua. Pertanto, non lo vedo come un anti-pattern, tanto quanto vorrei static_cast<bool>(num_value). Il cast potrebbe benissimo fornire i risultati corretti, ma alcuni compilatori emettono quindi un avviso sulle prestazioni, quindi devi ancora affrontarlo.

L'altro modo per affrontare questo è da dire, (num_value != FALSE). Mi va bene anche questo, ma tutto sommato !!num_valueè molto meno prolisso, può essere più chiaro e non crea confusione la seconda volta che lo vedi.


2

!! è stato utilizzato per far fronte al C ++ originale che non aveva un tipo booleano (come nemmeno C).


Problema di esempio:

Dentro if(condition), la conditionnecessità di valutare qualche tipo come double, int, void*, ecc., Ma non boolperché ancora non esiste.

Supponiamo che esista una classe int256(un numero intero a 256 bit) e tutte le conversioni / cast di interi siano stati sovraccaricati.

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

Per verificare se xera "vero" o diverso da zero, if (x)converteva xin un numero intero e quindi valutava se intera diverso da zero. Un tipico sovraccarico di (int) xrestituirebbe solo i bit LS di x. if (x)allora stava solo testando gli LSbits di x.

Ma C ++ ha l' !operatore. Un sovraccarico in !xgenere valuta tutti i bit di x. Quindi per tornare alla logica non invertita if (!!x)viene utilizzata.

Rif Le versioni precedenti di C ++ usavano l'operatore `int` di una classe quando valutava la condizione in un'istruzione` if () `?


1

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

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

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

  • vuoi semplicemente convertire qualcosa in un valore 0/1 rigoroso. In C ++ un assegnamento a a boollo 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)varietà da solo.

Penso che nel contesto di un'espressione booleana più ampia, semplicemente ingombra le cose.


1

Se la variabile è di tipo oggetto, potrebbe avere un! operatore definito ma nessun cast in bool (o peggio un cast implicito in int con semantica diversa. Chiamare due volte l'operatore! risulta in una conversione in bool che funziona anche in casi strani.


0

È corretto ma, in C, inutile qui - "if" e "&&" tratterebbero l'espressione allo stesso modo senza "!!".

Il motivo per farlo in C ++, suppongo, è che '&&' potrebbe essere sovraccarico. Ma allora, così potrebbe '!', Quindi non garantisce realmente di ottenere un bool, senza guardare il codice per i tipi di variablee api.call. Forse qualcuno con più esperienza C ++ potrebbe spiegare; forse è inteso come una sorta di misura di difesa in profondità, non come una garanzia.


Il compilatore tratterebbe i valori allo stesso modo se è usato solo come operando per un ifo &&, ma l'uso !!può aiutare su alcuni compilatori se if (!!(number & mask))viene sostituito con bit triggered = !!(number & mask); if (triggered); su alcuni compilatori incorporati con tipi di bit, assegnare ad esempio 256 a un tipo di bit produrrà zero. Senza il !!, la trasformazione apparentemente sicura (copiare la ifcondizione in una variabile e poi ramificarsi) non sarà sicura.
supercat

0

Forse i programmatori stavano pensando qualcosa del genere ...

!! myAnswer è booleano. Nel contesto, dovrebbe diventare booleano, ma adoro sbattere le cose per essere sicuro, perché una volta c'era un insetto misterioso che mi ha morso, e bang bang, l'ho ucciso.


0

Questo potrebbe essere un esempio del trucco del double-bang , vedere 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 booloperatore di conversione.

// 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 classe,

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 senza senso come test << 1;o int i=test.

L'utilizzo operator!è più sicuro perché evitiamo conversioni implicite o problemi di sovraccarico.

L'implementazione è banale,

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

I due modi idiomatici per testare l' Testableoggetto 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 alcune persone chiamano il trucco del doppio colpo .


1
A partire da C ++ 11, è possibile utilizzare explicit operator boolper impedire la conversione implicita in altri tipi integrali.
Arne Vogel
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.