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 ++?
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 ++?
Risposte:
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 :-)
(value ? function1 : function2)()
.
function1
e function2
vengono convertiti implicitamente in puntatori a funzione, e il risultato viene convertito implicitamente di nuovo.
Puoi inserire gli URI nel codice sorgente C ++ senza errori. Per esempio:
void foo() {
http://stackoverflow.com/
int bar = 4;
...
}
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.
goto http;
realtà non segue l'URL. :(
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.
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 )
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 );
using
.
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.
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 ... :-)
A
non importa affatto. Ad esempio, se A
fosse a char*
, il codice sarebbe ancora valido.
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).
From
e To
sono impostati e utilizzati di conseguenza. Tale unione può essere utilizzata con un comportamento definito ( To
essendo 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.
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.
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
+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.
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);
}
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
}
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
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.
return
catturare il blocco di Function Try, solo rilanciare.
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; };
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;
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 :)
if((a = f()) == b) ...
, ma questa risposta in realtà dichiara una variabile nella condizione.
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 :)
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.
Inizializzazione dell'array nel costruttore. Ad esempio in una classe se abbiamo un array di int
as:
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()
{
}
Oooh, posso invece trovare un elenco di animali che odiano:
Il lato positivo
È 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 C
oggetto, nel qual caso la classe B
non 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::stack
esempio.
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);
}
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".
Caratteristiche nascoste:
Se una funzione genera un'eccezione non elencata nelle sue specifiche di eccezione, ma la funzione ha std::bad_exception
nella sua specifica di eccezione, l'eccezione viene convertita in std::bad_exception
e lanciata automaticamente. In questo modo saprai almeno che a è bad_exception
stato lanciato. Leggi di più qui .
funzione prova blocchi
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 .
i valori predefiniti dei parametri della funzione possono essere modificati in fase di esecuzione. Leggi di più qui .
A[i]
funziona bene come i[A]
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 .
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)
}
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.
.find()
.
const map::operator[]
genera messaggi di errore"
Mettere funzioni o variabili in uno spazio dei nomi senza nome depreca l'uso di static
limitarle all'ambito del file.
static
in ambito globale non è in alcun modo deprecato. (Per riferimento: C ++ 03 §D.2)
static
use dovrebbe essere usato solo all'interno di un tipo di classe o di una funzione.
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
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 void
funzioni 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; !
};
Leggi un file in un vettore di stringhe:
vector<string> V;
copy(istream_iterator<string>(cin), istream_iterator<string>(),
back_inserter(V));
vector<string> V((istream_iterator<string>(cin)), istream_iterator<string>());
- parentesi mancanti dopo il secondo parametro
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.
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 SomeType
oggetto nello stack e lo inizializzano (con u
nei 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 SomeType
denominato u
.
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;
FStruct s = {};
è ancora più breve.
main
? Suggerirei di global().main();
dimenticare il singleton ( puoi semplicemente lavorare con il temporaneo, che ne prolunga la durata )
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).
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.
struct C
nel tuo esempio ...? Saluti.