Funzionalità nascoste di C ++? [chiuso]


114

Nessun C ++ ama quando si tratta delle "caratteristiche nascoste" della linea di domande? Ho pensato di buttarlo là fuori. Quali sono alcune delle funzionalità nascoste di C ++?


@ Devtron - Ho visto alcuni bug fantastici (cioè comportamenti inaspettati) venduti come funzionalità. In effetti, l'industria dei giochi in realtà cerca di farlo accadere al giorno d'oggi e lo chiama "gameplay emergente" (anche, guarda "TK Surfing" di Psi-Ops, era puramente un bug, poi l'hanno lasciato così com'è ed è uno dei migliori caratteristiche del gioco IMHO)
Grant Peters

5
@Laith J: Non molte persone hanno letto lo standard ISO C ++ di 786 pagine dall'inizio alla fine - ma suppongo che tu l'abbia fatto, e tu l'abbia mantenuto tutto, giusto?
j_random_hacker

2
@Laith, @j_random: Vedi la mia domanda "Cos'è lo scherzo di un programmatore, come lo riconosco e qual è la risposta appropriata" su stackoverflow.com/questions/1/you-have-been-link-rolled .

Risposte:


308

La maggior parte dei programmatori C ++ ha familiarità con l'operatore ternario:

x = (y < 0) ? 10 : 20;

Tuttavia, non si rendono conto che può essere utilizzato come lvalue:

(a == 0 ? a : b) = 1;

che è una scorciatoia per

if (a == 0)
    a = 1;
else
    b = 1;

Usare con cautela :-)


11
Molto interessante. Posso vedere che crea un codice illeggibile però.
Jason Baker

112
Yikes. (a == 0? a: b) = (y <0? 10:20);
Jasper Bekkers

52
(b? trueCount: falseCount) ++
Pavel Radzivilovsky

12
Non so se si tratta di GCC specifico, ma sono rimasto sorpreso di trovare anche questo ha funzionato: (value ? function1 : function2)().
Chris Burt-Brown

3
@ Chris Burt-Brown: No, dovrebbe funzionare ovunque se hanno lo stesso tipo (cioè nessun argomento predefinito ) function1e function2vengono convertiti implicitamente in puntatori a funzione, e il risultato viene convertito implicitamente di nuovo.
MSalters

238

Puoi inserire gli URI nel codice sorgente C ++ senza errori. Per esempio:

void foo() {
    http://stackoverflow.com/
    int bar = 4;

    ...
}

41
Ma solo uno per funzione, sospetto? :)
Constantin

51
@jpoh: http seguito da due punti diventa una "etichetta" che utilizzerai in un'istruzione goto in seguito. ricevi quell'avvertimento dal tuo compilatore perché non è usato in nessuna istruzione goto nell'esempio precedente.
utku_karatas

9
Puoi aggiungerne più di uno purché abbiano protocolli diversi! ftp.microsoft.com gopher: //aerv.nl/1 e così via ...
Daniel Earwicker

4
@Pavel: un identificatore seguito da due punti è un'etichetta (da utilizzare con goto, che C ++ ha). Tutto ciò che segue due barre è un commento. Pertanto, con http://stackoverflow.com, httpè un'etichetta (potresti teoricamente scrivere goto http;) ed //stackoverflow.comè solo un commento di fine riga. Entrambi sono C ++ legali, quindi il costrutto viene compilato. Ovviamente non fa niente di vagamente utile.
David Thornley

8
Purtroppo in goto http;realtà non segue l'URL. :(
Yakov Galka

140

Aritmetica dei puntatori.

I programmatori C ++ preferiscono evitare i puntatori a causa dei bug che possono essere introdotti.

Il C ++ più bello che abbia mai visto però? Letterali analogici.


11
Evitiamo i puntatori a causa di bug? I puntatori sono fondamentalmente tutto ciò che riguarda la codifica C ++ dinamica!
Nick Bedford

1
I letterali analogici sono ottimi per le voci di contest C ++ offuscate, in particolare il tipo ASCII-art.
Synetech

119

Sono d'accordo con la maggior parte dei post: C ++ è un linguaggio multi-paradigma, quindi le caratteristiche "nascoste" che troverai (oltre ai "comportamenti indefiniti" che dovresti evitare a tutti i costi) sono usi intelligenti delle strutture.

La maggior parte di queste strutture non sono caratteristiche integrate del linguaggio, ma basate su libreria.

La più importante è la RAII , spesso ignorata per anni dagli sviluppatori C ++ provenienti dal mondo C. Sovraccarico dell'operatore è spesso una caratteristica fraintesa che abilita sia il comportamento di tipo array (operatore di indice), sia operazioni di tipo puntatore (puntatori intelligenti) e operazioni di tipo build-in (moltiplicazione di matrici.

L'uso dell'eccezione è spesso difficile, ma con un po 'di lavoro può produrre codice davvero robusto attraverso la sicurezza delle eccezioni specifiche di (incluso il codice che non fallirà o che avrà caratteristiche simili al commit che avrà successo o tornerà a il suo stato originale).

La più famosa delle funzionalità "nascoste" di C ++ è la metaprogrammazione dei template , in quanto ti permette di avere il tuo programma parzialmente (o totalmente) eseguito in fase di compilazione invece che a runtime. Questo è difficile, tuttavia, e devi avere una solida conoscenza dei modelli prima di provarlo.

Altri fanno uso del paradigma multiplo per produrre "modi di programmazione" al di fuori dell'antenato di C ++, cioè C.

Utilizzando funtori , è possibile simulare funzioni, con la sicurezza del tipo aggiuntiva e con stato. Utilizzando il modello di comando , è possibile ritardare l'esecuzione del codice. La maggior parte degli altri modelli di progettazione possono essere implementati in modo semplice ed efficiente in C ++ per produrre stili di codifica alternativi che non dovrebbero essere inclusi nell'elenco dei "paradigmi C ++ ufficiali".

Usando i modelli , puoi produrre codice che funzionerà sulla maggior parte dei tipi, incluso non quello che pensavi all'inizio. Puoi anche aumentare la sicurezza dei tipi (come un malloc / realloc / free automatizzato). Le funzionalità degli oggetti C ++ sono davvero potenti (e quindi pericolose se usate con noncuranza), ma anche il polimorfismo dinamico ha la sua versione statica in C ++: il CRTP .

Ho scoperto che la maggior parte dei libri di tipo " C ++ efficace " di Scott Meyers o dei libri di tipo " C ++ eccezionale " di Herb Sutter sono sia facili da leggere, sia piuttosto tesori di informazioni sulle caratteristiche note e meno note di C ++.

Tra i miei preferiti ce n'è uno che dovrebbe far crescere i capelli di qualsiasi programmatore Java dall'orrore: in C ++, il modo più orientato agli oggetti per aggiungere una funzionalità a un oggetto è attraverso una funzione non-amico non membro, invece di un membro- funzione (cioè metodo di classe), perché:

  • In C ++, l'interfaccia di una classe è sia le sue funzioni membro che le funzioni non membro nello stesso spazio dei nomi

  • le funzioni non-amico non membro non hanno accesso privilegiato alla classe interna. Pertanto, l'utilizzo di una funzione membro su una non membro non amico indebolirà l'incapsulamento della classe.

Questo non manca mai di sorprendere anche gli sviluppatori esperti.

(Fonte: tra gli altri, il Guru della settimana n. 84 di Herb Sutter: http://www.gotw.ca/gotw/084.htm )


+1 risposta molto approfondita. è incompleto per ovvie ragioni (altrimenti non ci sarebbero più "funzionalità nascoste"!): p nel primo punto alla fine della risposta, hai menzionato i membri di un'interfaccia di classe. vuoi dire ".. sia le sue funzioni membro che le funzioni amico non membro"?
Wilhelmtell


quello di cui parli con 1 deve essere koenig lookup, giusto?
Özgür

1
@wilhelmtell: No no no ... :-p ... Voglio dire "le sue funzioni membro e le funzioni non membro NON AMICHE" .... La ricerca di Koenig farà in modo che queste funzioni vengano considerate prima delle altre " outside "funziona nella sua ricerca di simboli
paercebal

7
Ottimo post e +1 soprattutto per l'ultima parte, di cui troppe poche persone si rendono conto. Probabilmente aggiungerei anche la libreria Boost come "funzionalità nascosta". La considero praticamente la libreria standard che C ++ avrebbe dovuto avere. ;)
jalf

118

Una caratteristica del linguaggio che considero in qualche modo nascosta, perché non ne avevo mai sentito parlare durante tutto il mio tempo a scuola, è l'alias dello spazio dei nomi. Non è stato portato alla mia attenzione fino a quando non mi sono imbattuto in esempi di esso nella documentazione del boost. Ovviamente, ora che lo so, puoi trovarlo in qualsiasi riferimento C ++ standard.

namespace fs = boost::filesystem;

fs::path myPath( strPath, fs::native );

1
Immagino che questo sia utile se non vuoi usare using.
Siqi Lin

4
È anche utile come un modo per passare da una implementazione all'altra, scegliendo di dire thread-safe contro non-thread-safe, o versione 1 contro 2.
Tony Delroy

3
È particolarmente utile se stai lavorando a un progetto molto grande con gerarchie di spazi dei nomi di grandi dimensioni e non vuoi che le tue intestazioni causino inquinamento dello spazio dei nomi (e vuoi che le tue dichiarazioni di variabili siano leggibili dall'uomo).
Brandon Bohrer

102

Non solo le variabili possono essere dichiarate nella parte init di un file for ciclo, ma anche classi e funzioni.

for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) {
    ...
}

Ciò consente più variabili di tipi diversi.


31
È bello sapere che puoi farlo, ma personalmente proverei davvero a evitare di fare qualcosa del genere. Soprattutto perché è difficile da leggere.
Zoomulator

2
In realtà, ciò che funzionerebbe in questo contesto è usare una coppia: for (std :: pair <int, float> loop = std :: make_pair (1,2); loop.first> 0; loop.second + = 1)
Valentin Heinitz

2
@Valentin, allora ti consiglio di provare a fare una segnalazione di bug contro VS2008 invece di downvoting della funzione nascosta. È chiaramente colpa del tuo compilatore.
Johannes Schaub - litb

2
Hmm, non funziona neanche in msvc10. Che tristezza :(
avakar

2
@avakar infatti, gcc ha introdotto un bug che lo fa rifiutare anche nella v4.6 :) vedi gcc.gnu.org/bugzilla/show_bug.cgi?id=46791
Johannes Schaub - litb

77

L'operatore di matrice è associativo.

A [8] è un sinonimo di * (A + 8). Poiché l'addizione è associativa, può essere riscritta come * (8 + A), che è un sinonimo di ..... 8 [A]

Non hai detto utile ... :-)


15
In realtà, quando usi questo trucco, dovresti davvero prestare attenzione al tipo che stai usando. A [8] è in realtà l'8 ° A mentre 8 [A] è l'intero Ath che inizia all'indirizzo 8. Se A è un byte, hai un bug.
Vincent Robert,

38
intendi "commutativo" dove dici "associativo"?
DarenW

28
Vincent, ti sbagli. Il tipo di Anon importa affatto. Ad esempio, se Afosse a char*, il codice sarebbe ancora valido.
Konrad Rudolph,

11
Attenzione che A deve essere un puntatore e non un operatore di sovraccarico di classi [].
David Rodríguez - dribeas

15
Vincent, in questo ci devono essere un tipo integrale e un tipo puntatore, e né C né C ++ si preoccupano di quale va per primo.
David Thornley

73

Una cosa poco nota è che anche i sindacati possono essere modelli:

template<typename From, typename To>
union union_cast {
    From from;
    To   to;

    union_cast(From from)
        :from(from) { }

    To getTo() const { return to; }
};

E possono avere anche costruttori e funzioni membro. Niente che abbia a che fare con l'ereditarietà (comprese le funzioni virtuali).


Interessante! Quindi, devi inizializzare tutti i membri? Segue il solito ordine della struttura, implicando che l'ultimo membro sarà inizializzato "sopra" i membri precedenti?
j_random_hacker

j_random_hacker oh, giusto che non ha senso. buona pesca. l'ho scritto come se fosse una struttura. aspetta lo aggiusterò
Johannes Schaub - litb

Questo non richiama un comportamento indefinito?
Greg Bacon

7
@gbacon, sì, richiama un comportamento non definito se Frome Tosono impostati e utilizzati di conseguenza. Tale unione può essere utilizzata con un comportamento definito ( Toessendo un array di caratteri non firmati o una struttura che condivide una sequenza iniziale con From). Anche se lo usi in modo indefinito, potrebbe comunque essere utile per lavori di basso livello. Ad ogni modo, questo è solo un esempio di un modello di unione: potrebbero esserci altri usi per un'unione basata su modelli.
Johannes Schaub - litb

3
Attento al costruttore. Nota che devi costruire solo il primo elemento ed è consentito solo in C ++ 0x. A partire dallo standard attuale, devi attenersi a tipi banalmente costruibili. E nessun distruttore.
Potatoswatter

72

C ++ è uno standard, non dovrebbero esserci funzionalità nascoste ...

Il C ++ è un linguaggio multi-paradigma, puoi scommettere i tuoi ultimi soldi sulla presenza di funzionalità nascoste. Un esempio tra tanti: la metaprogrammazione dei template . Nessuno nel comitato per gli standard intendeva che ci fosse un sottolingua completo di Turing che viene eseguito in fase di compilazione.


65

Un'altra caratteristica nascosta che non funziona in C è la funzionalità dell'unario + dell'operatore . Puoi usarlo per promuovere e decadere ogni sorta di cose

Conversione di un'enumerazione in un numero intero

+AnEnumeratorValue

E il valore dell'enumeratore che in precedenza aveva il tipo di enumerazione ora ha il tipo intero perfetto che può adattarsi al suo valore. Manualmente, difficilmente conosceresti quel tipo! Ciò è necessario ad esempio quando si desidera implementare un operatore sovraccarico per l'enumerazione.

Ottieni il valore da una variabile

Devi usare una classe che utilizza un inizializzatore statico in classe senza una definizione fuori classe, ma a volte non riesce a collegarsi? L'operatore può aiutare a creare un temporaneo senza fare ipotesi o dipendenze dal suo tipo

struct Foo {
  static int const value = 42;
};

// This does something interesting...
template<typename T>
void f(T const&);

int main() {
  // fails to link - tries to get the address of "Foo::value"!
  f(Foo::value);

  // works - pass a temporary value
  f(+Foo::value);
}

Decadere un array in un puntatore

Vuoi passare due puntatori a una funzione, ma semplicemente non funzionerà? L'operatore può aiutare

// This does something interesting...
template<typename T>
void f(T const& a, T const& b);

int main() {
  int a[2];
  int b[3];
  f(a, b); // won't work! different values for "T"!
  f(+a, +b); // works! T is "int*" both time
}

61

La durata dei provvisori legati a riferimenti cost è di cui poche persone conoscono. O almeno è il mio pezzo preferito di conoscenza del C ++ che la maggior parte delle persone non conosce.

const MyClass& x = MyClass(); // temporary exists as long as x is in scope

3
Puoi elaborare? Come stai solo scherzando;)
Joseph Garvin,

8
ScopeGuard ( ddj.com/cpp/184403758 ) è un ottimo esempio che sfrutta questa funzionalità.
MSN

2
Sono con Joseph Garvin. Per favore, illuminaci.
Peter Mortensen

L'ho appena fatto nei commenti. Inoltre, è una conseguenza naturale dell'utilizzo di un parametro di riferimento const.
MSN


52

Una caratteristica interessante che non viene utilizzata spesso è il blocco try-catch a livello di funzione:

int Function()
try
{
   // do something here
   return 42;
}
catch(...)
{
   return -1;
}

L'utilizzo principale sarebbe tradurre l'eccezione in un'altra classe di eccezioni e rilanciarla, o tradurre tra eccezioni e gestione del codice di errore basata sulla restituzione.


Non penso che tu possa returncatturare il blocco di Function Try, solo rilanciare.
Constantin

Ho appena provato a compilare quanto sopra e non ha dato alcun avviso. Penso che l'esempio sopra funzioni.
vividos

7
il ritorno è vietato solo per i costruttori. Il blocco try della funzione di un costruttore rileverà gli errori nell'inizializzazione della base e dei membri (l'unico caso in cui un blocco try della funzione fa qualcosa di diverso dal semplice tentativo all'interno della funzione); non rilanciare risulterebbe in un oggetto incompleto.
puetzk

Sì. Questo è molto utile. Ho scritto macro BEGIN_COM_METHOD e END_COM_METHOD per rilevare le eccezioni e restituire HRESULTS in modo che le eccezioni non trapelassero da una classe che implementa un'interfaccia COM. Ha funzionato bene.
Scott Langham

3
Come sottolineato da @puetzk, questo è l'unico modo per gestire le eccezioni lanciate da qualsiasi cosa nell'elenco di inizializzatori di un costruttore , come i costruttori delle classi base o quelli dei membri dati.
anton.burger

44

Molti conoscono la metafunzione identity/ id, ma c'è un bel caso d'uso per i casi senza modello: Facilità di scrittura delle dichiarazioni:

// void (*f)(); // same
id<void()>::type *f;

// void (*f(void(*p)()))(int); // same
id<void(int)>::type *f(id<void()>::type *p);

// int (*p)[2] = new int[10][2]; // same
id<int[2]>::type *p = new int[10][2];

// void (C::*p)(int) = 0; // same
id<void(int)>::type C::*p = 0;

Aiuta a decrittografare le dichiarazioni C ++ notevolmente!

// boost::identity is pretty much the same
template<typename T> 
struct id { typedef T type; };

Interessante, ma inizialmente ho avuto più problemi a leggere alcune di queste definizioni. Un altro modo per risolvere il problema al template<typename Ret,typename... Args> using function = Ret (Args...); template<typename T> using pointer = *T;pointer<function<void,int>> f(pointer<function<void,void>>);pointer<void(int)> f(pointer<void()>);function<pointer<function<void,int>>,pointer<function<void,void>>> f;
rovescio

42

Una caratteristica abbastanza nascosta è che puoi definire variabili all'interno di una condizione if e il suo ambito si estenderà solo sui blocchi if e else:

if(int * p = getPointer()) {
    // do something
}

Alcune macro lo usano, ad esempio per fornire un ambito "bloccato" come questo:

struct MutexLocker { 
    MutexLocker(Mutex&);
    ~MutexLocker(); 
    operator bool() const { return false; } 
private:
    Mutex &m;
};

#define locked(mutex) if(MutexLocker const& lock = MutexLocker(mutex)) {} else 

void someCriticalPath() {
    locked(myLocker) { /* ... */ }
}

Anche BOOST_FOREACH lo usa sotto il cofano. Per completare questo, non solo è possibile in un if, ma anche in uno switch:

switch(int value = getIt()) {
    // ...
}

e in un ciclo while:

while(SomeThing t = getSomeThing()) {
    // ...
}

(e anche in una condizione for). Ma non sono molto sicuro che siano così utili :)


! Neat Non sapevo che potessi farlo ... avrebbe (e salverà) alcuni problemi durante la scrittura di codice con valori di ritorno di errore. C'è un modo per avere ancora un condizionale invece di solo! = 0 in questa forma? if ((int r = func ()) <0) non sembra funzionare ...
puetzk

puetzk, no non c'è. ma felice che ti piaccia :)
Johannes Schaub - litb

4
@Frerich, questo non è affatto possibile nel codice C. Penso che tu stia pensando if((a = f()) == b) ..., ma questa risposta in realtà dichiara una variabile nella condizione.
Johannes Schaub - litb

1
@Angry è molto diverso, perché la dichiarazione della variabile viene testata immediatamente per il suo valore booleano. C'è anche una mappatura per i cicli for, che sembra che for(...; int i = foo(); ) ...;Questo passerà attraverso il corpo finché iè vero, inizializzandolo di nuovo ogni volta. Il ciclo che mostri sta semplicemente dimostrando una dichiarazione di variabile, ma non una dichiarazione di variabile che funge simultaneamente da condizione :)
Johannes Schaub - litb

5
Molto buono, tranne per il fatto che non hai menzionato l'uso previsto di questa funzione per cast di puntatori dinamici, credo.
mmocny

29

Impedire all'operatore virgola di chiamare gli overload dell'operatore

A volte fai un uso valido dell'operatore virgola, ma vuoi assicurarti che nessun operatore virgola definito dall'utente si intrometta, perché ad esempio ti affidi a punti di sequenza tra il lato sinistro e destro o vuoi assicurarti che nulla interferisca con il desiderato azione. È qui che void()entra in gioco:

for(T i, j; can_continue(i, j); ++i, void(), ++j)
  do_code(i, j);

Ignora i segnaposto che ho inserito per la condizione e il codice. Ciò che è importante è il void(), che costringe il compilatore a usare l'operatore virgola incorporato. Questo può essere utile anche quando si implementano classi di tratti.


L'ho usato solo per finire la mia espressione eccessiva da ignorante . :)
GManNickG

28

Inizializzazione dell'array nel costruttore. Ad esempio in una classe se abbiamo un array di intas:

class clName
{
  clName();
  int a[10];
};

Possiamo inizializzare tutti gli elementi nell'array al suo valore predefinito (qui tutti gli elementi dell'array a zero) nel costruttore come:

clName::clName() : a()
{
}

6
Puoi farlo con qualsiasi array ovunque.
Potatoswatter

@ Potatoswatter: più difficile di quanto sembri, a causa dell'analisi più irritante. Non riesco a pensare a nessun altro che possa essere fatto, tranne forse un valore di ritorno
Mooing Duck

Se il tipo dell'array è un tipo di classe, non è necessario, giusto?
Thomas Eding,

27

Oooh, posso invece trovare un elenco di animali che odiano:

  • I distruttori devono essere virtuali se intendi utilizzarli in modo polimorfico
  • A volte i membri vengono inizializzati per impostazione predefinita, a volte non lo sono
  • Le classi locali non possono essere utilizzate come parametri del modello (le rende meno utili)
  • specificatori di eccezione: sembrano utili, ma non lo sono
  • i sovraccarichi di funzioni nascondono le funzioni della classe base con firme diverse.
  • nessuna standardizzazione utile sull'internazionalizzazione (set di caratteri wide standard portatile, chiunque? Dovremo aspettare fino a C ++ 0x)

Il lato positivo

  • caratteristica nascosta: funzione prova blocchi. Purtroppo non ne ho trovato un uso. Sì, so perché l'hanno aggiunto, ma devi rilanciare in un costruttore che lo rende inutile.
  • Vale la pena esaminare attentamente le garanzie STL sulla validità dell'iteratore dopo la modifica del contenitore, che può consentire di creare loop leggermente più belli.
  • Boost: non è certo un segreto ma vale la pena usarlo.
  • Ottimizzazione del valore di ritorno (non ovvio, ma specificamente consentito dallo standard)
  • Functors aka oggetti funzione aka operator (). Questo è ampiamente utilizzato dal STL. non è proprio un segreto, ma è un elegante effetto collaterale del sovraccarico dell'operatore e dei modelli.

16
pet hate: nessuna ABI definita per le app C ++, a differenza di quelle C che tutti usano perché ogni linguaggio può garantire di chiamare una funzione C, nessuno può fare lo stesso per C ++.
gbjbaanb

8
I distruttori devono essere virtuali solo se intendi distruggere polimorficamente, il che è leggermente diverso dal primo punto.
David Rodríguez - dribeas

2
Con C ++ 0x i tipi locali possono essere usati come parametri del modello.
tstenner

1
Con C ++ 0x, i distruttori saranno virtuali se l'oggetto ha funzioni virtuali (ad esempio una tabella v).
Macke

non dimenticare NRVO, e ovviamente qualsiasi ottimizzazione è consentita fintanto che non cambia l'output del programma
jk.

26

È possibile accedere a dati protetti e membri di funzioni di qualsiasi classe, senza un comportamento indefinito e con la semantica prevista. Continua a leggere per vedere come. Leggi anche il rapporto sui difetti al riguardo.

Normalmente, C ++ vieta di accedere a membri protetti non statici di un oggetto di una classe, anche se quella classe è la vostra classe base

struct A {
protected:
    int a;
};

struct B : A {
    // error: can't access protected member
    static int get(A &x) { return x.a; }
};

struct C : A { };

È vietato: tu e il compilatore non sapete a cosa punta effettivamente il riferimento. Potrebbe essere un Coggetto, nel qual caso la classe Bnon ha nulla a che fare con i suoi dati. Tale accesso è concesso solo se xè un riferimento a una classe derivata oa una derivata da essa. E potrebbe consentire a parti di codice arbitrarie di leggere qualsiasi membro protetto semplicemente creando una classe "usa e getta" che legge i membri, ad esempio std::stack:

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        static std::deque<int> &get(std::stack<int> &s) {
            // error: stack<int>::c is protected
            return s.c;
        }
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = pillager::get(s);
}

Sicuramente, come vedi questo causerebbe troppi danni. Ma ora, i puntatori dei membri consentono di aggirare questa protezione! Il punto chiave è che il tipo di un puntatore a un membro è associato alla classe che effettivamente contiene detto membro, non alla classe che hai specificato quando hai preso l'indirizzo. Questo ci consente di aggirare il controllo

struct A {
protected:
    int a;
};

struct B : A {
    // valid: *can* access protected member
    static int get(A &x) { return x.*(&B::a); }
};

struct C : A { };

E, naturalmente, funziona anche con l' std::stackesempio.

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        static std::deque<int> &get(std::stack<int> &s) {
            return s.*(pillager::c);
        }
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = pillager::get(s);
}

Sarà ancora più semplice con una dichiarazione using nella classe derivata, che rende pubblico il nome del membro e fa riferimento al membro della classe base.

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        using std::stack<int>::c;
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = s.*(&pillager::c);
}


26

Un'altra caratteristica nascosta è che puoi chiamare oggetti di classe che possono essere convertiti in puntatori o riferimenti a funzioni. La risoluzione del sovraccarico viene eseguita in base al risultato e gli argomenti vengono inoltrati perfettamente.

template<typename Func1, typename Func2>
class callable {
  Func1 *m_f1;
  Func2 *m_f2;

public:
  callable(Func1 *f1, Func2 *f2):m_f1(f1), m_f2(f2) { }
  operator Func1*() { return m_f1; }
  operator Func2*() { return m_f2; }
};

void foo(int i) { std::cout << "foo: " << i << std::endl; }
void bar(long il) { std::cout << "bar: " << il << std::endl; }

int main() {
  callable<void(int), void(long)> c(foo, bar);
  c(42); // calls foo
  c(42L); // calls bar
}

Queste sono chiamate "funzioni di chiamata surrogata".


1
Quando dici che la risoluzione del sovraccarico viene eseguita sul risultato di essi, intendi dire che in realtà la converte in entrambi i Functor e quindi la risoluzione del sovraccarico? Ho provato a stampare qualcosa nell'operatore Func1 * () e nell'operatore Func2 * (), ma sembra che scelga quello corretto quando capisce quale operatore di conversione invocare.
navigatore

3
@navigator, sì, converte concettualmente in entrambi e quindi sceglie il meglio. Non ha bisogno di chiamarli effettivamente, perché sa dal tipo di risultato cosa produrranno già. La chiamata effettiva viene eseguita quando si scopre ciò che è stato finalmente scelto.
Johannes Schaub - litb

26

Caratteristiche nascoste:

  1. Le funzioni virtuali pure possono essere implementate. Esempio comune, distruttore virtuale puro.
  2. Se una funzione genera un'eccezione non elencata nelle sue specifiche di eccezione, ma la funzione ha std::bad_exceptionnella sua specifica di eccezione, l'eccezione viene convertita in std::bad_exceptione lanciata automaticamente. In questo modo saprai almeno che a è bad_exceptionstato lanciato. Leggi di più qui .

  3. funzione prova blocchi

  4. La parola chiave template per disambiguare i typedef in un modello di classe. Se il nome di una specializzazione template membro appare dopo una ., ->o ::dell'operatore, e che nome ha parametri di modello esplicitamente qualificati, prefisso il nome del modello membro con la parola chiave modello. Leggi di più qui .

  5. i valori predefiniti dei parametri della funzione possono essere modificati in fase di esecuzione. Leggi di più qui .

  6. A[i] funziona bene come i[A]

  7. Le istanze temporanee di una classe possono essere modificate! Una funzione membro non const può essere richiamata su un oggetto temporaneo. Per esempio:

    struct Bar {
      void modify() {}
    }
    int main (void) {
      Bar().modify();   /* non-const function invoked on a temporary. */
    }

    Leggi di più qui .

  8. Se sono presenti due tipi diversi prima e dopo l' espressione :nell'operatore ternario ( ?:), il tipo risultante dell'espressione è quello che è il più generale dei due. Per esempio:

    void foo (int) {}
    void foo (double) {}
    struct X {
      X (double d = 0.0) {}
    };
    void foo (X) {} 
    
    int main(void) {
      int i = 1;
      foo(i ? 0 : 0.0); // calls foo(double)
      X x;
      foo(i ? 0.0 : x);  // calls foo(X)
    }

P Papà: A [i] == * (A + i) == * (i + A) == i [A]
abelenky

Ottengo la commutazione, è solo che questo significa che [] non ha un valore semantico proprio ed è semplicemente equivalente a una sostituzione in stile macro dove "x [y]" è sostituito con "(* ((x) + (y )))". Non è affatto quello che mi aspettavo. Mi chiedo perché sia ​​definito in questo modo.
P Daddy

Compatibilità con le versioni precedenti con C
jmucchiello

2
Per quanto riguarda il primo punto: C'è un caso particolare in cui si deve implementare una pura funzione virtuale: distruttori virtuali puri.
Frerich Raabe,

24

map::operator[]crea la voce se la chiave è mancante e restituisce il riferimento al valore della voce costruito per impostazione predefinita. Quindi puoi scrivere:

map<int, string> m;
string& s = m[42]; // no need for map::find()
if (s.empty()) { // assuming we never store empty values in m
  s.assign(...);
}
cout << s;

Sono stupito di quanti programmatori C ++ non lo sappiano.


11
E all'estremità opposta non è possibile utilizzare l'operatore [] su una mappa const
David Rodríguez - dribeas

2
+1 per Nick, le persone possono impazzire se non lo sanno .find().
LiraNuna

oppure " const map::operator[]genera messaggi di errore"
solo qualcuno il

2
Non è una caratteristica del linguaggio, è una caratteristica della libreria di modelli standard. È anche abbastanza ovvio, poiché l'operatore [] restituisce un riferimento valido.
Ramon Zarazua B.

2
Ho dovuto usare le mappe in C # per un po ', dove le mappe non si comportano in questo modo, per rendermi conto che questa è una funzionalità. Pensavo di esserne infastidito più di quanto lo usassi, ma sembra che mi sbagliavo. Mi manca in C #.
sbi

20

Mettere funzioni o variabili in uno spazio dei nomi senza nome depreca l'uso di staticlimitarle all'ambito del file.


"deprecates" è un termine forte…
Potatoswatter

@ Potato: vecchio commento, lo so, ma lo standard dice che l'uso di static nell'ambito dello spazio dei nomi è deprecato, con la preferenza per gli spazi dei nomi senza nome.
GManNickG

@ GMan: nessun problema, non credo che le pagine di SO "muoiano davvero". Solo per entrambi i lati della storia, staticin ambito globale non è in alcun modo deprecato. (Per riferimento: C ++ 03 §D.2)
Potatoswatter

Ah, a una lettura più attenta, "Un nome dichiarato nello spazio dei nomi globale ha un ambito dello spazio dei nomi globale (chiamato anche ambito globale)". Significa davvero questo?
Potatoswatter

@ Patato: Sì. :) staticuse dovrebbe essere usato solo all'interno di un tipo di classe o di una funzione.
GManNickG

19

La definizione delle normali funzioni di amicizia nei modelli di classe richiede un'attenzione speciale:

template <typename T> 
class Creator { 
    friend void appear() {  // a new function ::appear(), but it doesn't 
                           // exist until Creator is instantiated 
    } 
};
Creator<void> miracle;  // ::appear() is created at this point 
Creator<double> oops;   // ERROR: ::appear() is created a second time! 

In questo esempio, due diverse istanze creano due definizioni identiche: una violazione diretta dell'ODR

Dobbiamo quindi assicurarci che i parametri del modello del modello di classe appaiano nel tipo di qualsiasi funzione amico definita in quel modello (a meno che non si voglia impedire più di un'istanza di un modello di classe in un particolare file, ma questo è piuttosto improbabile). Applichiamo questo a una variazione del nostro esempio precedente:

template <typename T> 
class Creator { 
    friend void feed(Creator<T>*){  // every T generates a different 
                                   // function ::feed() 
    } 
}; 

Creator<void> one;     // generates ::feed(Creator<void>*) 
Creator<double> two;   // generates ::feed(Creator<double>*) 

Dichiarazione di non responsabilità: ho incollato questa sezione da Modelli C ++: Guida completa / Sezione 8.4


18

Le funzioni void possono restituire valori void

Poco conosciuto, ma il codice seguente va bene

void f() { }
void g() { return f(); }

Oltre al seguente strano aspetto

void f() { return (void)"i'm discarded"; }

Sapendo questo, puoi trarne vantaggio in alcune aree. Un esempio: le voidfunzioni non possono restituire un valore ma puoi anche non restituire semplicemente nulla, perché possono essere istanziate con non-void. Invece di memorizzare il valore in una variabile locale, che causerà un errore per void, restituisci direttamente un valore

template<typename T>
struct sample {
  // assume f<T> may return void
  T dosomething() { return f<T>(); }

  // better than T t = f<T>(); /* ... */ return t; !
};

17

Leggi un file in un vettore di stringhe:

 vector<string> V;
 copy(istream_iterator<string>(cin), istream_iterator<string>(),
     back_inserter(V));

istream_iterator


8
Oppure: vector <string> V ((istream_iterator <string> (cin)), istream_iterator <string>);
UncleBens

5
vuoi dire vector<string> V((istream_iterator<string>(cin)), istream_iterator<string>());- parentesi mancanti dopo il secondo parametro
knittl

1
Questa non è davvero una funzionalità C ++ nascosta. Più di una caratteristica STL. STL! = Una lingua
Nick Bedford

14

Puoi modellare i campi di bit.

template <size_t X, size_t Y>
struct bitfield
{
    char left  : X;
    char right : Y;
};

Devo ancora trovare uno scopo per questo, ma sicuramente mi ha sorpreso.


1
Vedi qui, dove ho recentemente suggerito che per n bit aritmetica: stackoverflow.com/questions/8309538/...
sehe

14

Una delle grammatiche più interessanti di qualsiasi linguaggio di programmazione.

Tre di queste cose appartengono insieme e due sono qualcosa di completamente diverso ...

SomeType t = u;
SomeType t(u);
SomeType t();
SomeType t;
SomeType t(SomeType(u));

Tutti tranne il terzo e il quinto definiscono un SomeTypeoggetto nello stack e lo inizializzano (con unei primi due casi e il costruttore predefinito nel quarto. Il terzo dichiara una funzione che non accetta parametri e restituisce a SomeType. Il quinto dichiara in modo simile una funzione che accetta un parametro per valore di tipo SomeTypedenominato u.


c'è qualche differenza tra il primo e il secondo? tuttavia, so che sono entrambe inizializzazioni.
Özgür

Comptrol: Non credo. Entrambi finiranno per chiamare il costruttore di copia, anche se il primo SEMBRA come l'operatore di assegnazione, in realtà è il costruttore di copia.
abelenky

1
Se u è un tipo diverso da SomeType, il primo chiamerà prima il costruttore di conversione e poi il costruttore di copia, mentre il secondo chiamerà solo il costruttore di conversione.
Eclipse

3
La prima è la chiamata implicita del costruttore, la seconda è la chiamata esplicita. Guarda il codice seguente per vedere la differenza: #include <iostream> class sss {public: explicit sss (int) {std :: cout << "int" << std :: endl; }; sss (double) {std :: cout << "double" << std :: endl; }; }; int main () {sss ddd (7); // chiama int costruttore sss xxx = 7; // chiama il doppio costruttore return 0; }
Kirill V. Lyadvinsky,

True: la prima riga non funzionerà se il costruttore è dichiarato esplicito.
Eclipse

12

Sbarazzarsi delle dichiarazioni previsionali:

struct global
{
     void main()
     {
           a = 1;
           b();
     }
     int a;
     void b(){}
}
singleton;

Scrittura di istruzioni switch con?: Operatori:

string result = 
    a==0 ? "zero" :
    a==1 ? "one" :
    a==2 ? "two" :
    0;

Fare tutto su una singola riga:

void a();
int b();
float c = (a(),b(),1.0f);

Azzerare le strutture senza memset:

FStruct s = {0};

Valori di tempo e angolo di normalizzazione / avvolgimento:

int angle = (short)((+180+30)*65536/360) * 360/65536; //==-150

Assegnazione dei riferimenti:

struct ref
{
   int& r;
   ref(int& r):r(r){}
};
int b;
ref a(b);
int c;
*(int**)&a = &c;

2
FStruct s = {};è ancora più breve.
Constantin

Nell'ultimo esempio, sarebbe più semplice con: a (); b (); float c = 1.0f;
Zifre

2
Questa sintassi "float c = (a (), b (), 1.0f);" è utile per accentuare l'operazione di assegnazione (assegnazione di "c"). Le operazioni di assegnazione sono importanti nella programmazione perché è meno probabile che diventino obsolete IMO. Non so perché, potrebbe avere qualcosa a che fare con la programmazione funzionale in cui lo stato del programma viene riassegnato a ogni frame. PS. E no, "int d = (11,22,1.0f)" sarà uguale a "1". Testato un minuto fa con VS2008.
AareP

2
+1 Non dovresti chiamare main ? Suggerirei di global().main();dimenticare il singleton ( puoi semplicemente lavorare con il temporaneo, che ne prolunga la durata )
vedi

1
Dubito che l'assegnazione dei riferimenti sia portabile. Adoro la struttura per rinunciare alle dichiarazioni in avanti però.
Thomas Eding,

12

L'operatore condizionale ternario ?:richiede che il suo secondo e terzo operando abbiano tipi "gradevoli" (parlando in modo informale). Ma questo requisito ha un'eccezione (gioco di parole): il secondo o il terzo operando può essere un'espressione di lancio (che ha typevoid ), indipendentemente dal tipo dell'altro operando.

In altre parole, è possibile scrivere le seguenti espressioni C ++ perfettamente valide utilizzando l' ?:operatore

i = a > b ? a : throw something();

A proposito, il fatto che throw expression sia in realtà un'espressione (di tipo void) e non un'istruzione è un'altra caratteristica poco conosciuta del linguaggio C ++. Ciò significa, tra le altre cose, che il seguente codice è perfettamente valido

void foo()
{
  return throw something();
}

anche se non ha molto senso farlo in questo modo (forse in qualche codice modello generico questo potrebbe tornare utile).


Per quello che vale, Neil ha una domanda su questo: stackoverflow.com/questions/1212978/… , solo per informazioni extra.
GManNickG

12

La regola della dominanza è utile, ma poco conosciuta. Dice che anche se in un percorso non univoco attraverso un reticolo della classe base, la ricerca del nome per un membro parzialmente nascosto è univoca se il membro appartiene a una classe base virtuale:

struct A { void f() { } };

struct B : virtual A { void f() { cout << "B!"; } };
struct C : virtual A { };

// name-lookup sees B::f and A::f, but B::f dominates over A::f !
struct D : B, C { void g() { f(); } };

L'ho usato per implementare il supporto dell'allineamento che calcola automaticamente l'allineamento più rigoroso per mezzo della regola di dominanza.

Questo non si applica solo alle funzioni virtuali, ma anche a nomi typedef, membri statici / non virtuali e qualsiasi altra cosa. L'ho visto usato per implementare tratti sovrascrivibili nei meta-programmi.


1
Neat. Qualche motivo particolare che hai incluso struct Cnel tuo esempio ...? Saluti.
Tony Delroy
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.