it-swarm.it

Funzionalità nascoste di C ++?

Nessun amore per il C++ quando si tratta delle "caratteristiche nascoste" della linea di domande? Ho pensato che l'avrei buttato là fuori. Quali sono alcune delle funzionalità nascoste di C++?

114
Craig H

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 usato come un valore:

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

che è una scorciatoia per

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

Usare con cautela :-)

308
Ferruccio

È possibile inserire gli URI nell'origine C++ senza errori. Per esempio:

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

    ...
}
238
Ben

Aritmetica del puntatore.

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

Il C++ più bello che abbia mai visto? valori letterali analogici.

140
Anonymouse

Sono d'accordo con la maggior parte dei post lì: C++ è un linguaggio multi-paradigma, quindi le funzionalità "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 funzionalità integrate nella lingua, ma basate su librerie.

Il più importante è il [~ # ~] raii [~ # ~] , spesso ignorato per anni dagli sviluppatori C++ provenienti dal mondo C. Il sovraccarico dell'operatore è spesso una funzione incompresa che abilita sia comportamenti simili a array (operatore di sottoscrizione), operazioni simil-puntatore (puntatori intelligenti) e build-in-like operazioni (moltiplicando le matrici.

L'uso dell'eccezione è spesso difficile, ma con un po 'di lavoro può produrre un codice veramente affidabile attraverso la sicurezza dell'eccezione specifiche (incluso il codice che non fallirà o che avrà funzioni simili a commit che avranno esito positivo o che tornerà al suo stato originale).

La più famosa delle funzionalità "nascoste" di C++ è metaprogrammazione del modello , in quanto consente di eseguire parzialmente (o totalmente) il programma al momento della compilazione invece di 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 programmare" al di fuori dell'antenato del C++, cioè C.

Usando functors , puoi simulare le funzioni, con la sicurezza aggiuntiva del tipo ed essendo stateful. Utilizzando il modello comando , è possibile ritardare l'esecuzione del codice. La maggior parte degli altri modelli di progettazione possono essere facilmente ed efficientemente implementati in C++ per produrre stili di codifica alternativi che non dovrebbero essere inclusi nell'elenco dei "paradigmi C++ ufficiali".

Usando 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 typesafe automatizzato malloc/realloc/free). Le caratteristiche 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 " C++ eccezionale" - sono entrambi facili da leggere e abbastanza tesori di informazioni su funzionalità note e meno conosciute di C++.

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

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

  • le funzioni non membri non amiche non hanno accesso privilegiato alla classe interna. Pertanto, l'uso di una funzione membro su una non amica non membro indebolirà l'incapsulamento della classe.

Questo non manca mai di sorprendere anche sviluppatori esperti.

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

119
paercebal

Una caratteristica della lingua che considero in qualche modo nascosta, perché non ne avevo mai sentito parlare durante tutto il mio tempo a scuola, è lo pseudonimo dello spazio dei nomi. Non è stato portato alla mia attenzione fino a quando non mi sono imbattuto in esempi nella documentazione boost. Naturalmente, ora che ne sono a conoscenza, puoi trovarlo in qualsiasi riferimento C++ standard.

namespace fs = boost::filesystem;

fs::path myPath( strPath, fs::native );
118
Jason Mock

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

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

Ciò consente più variabili di diversi tipi.

102

L'operatore dell'array è associativo.

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

Non hai detto utile ... :-)

77
Colin Jensen

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. Nulla ha a che fare con l'ereditarietà (comprese le funzioni virtuali).

73

Il C++ è uno standard, non dovrebbero esserci funzioni nascoste ...

C++ è un linguaggio multi-paradigma, puoi scommettere i tuoi ultimi soldi sul fatto che ci sono funzioni nascoste. Un esempio su molti: metaprogramming del modello . Nessuno nel comitato per gli standard intendeva che ci fosse una sublanguage completa di Turing che viene eseguita in fase di compilazione.

72
Konrad Rudolph

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

Conversione di un'enumerazione in un numero intero

+AnEnumeratorValue

E il valore del tuo enumeratore che in precedenza aveva il suo 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 il proprio conteggio.

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 creare assunti 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 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 vita dei temporali legati a riferimenti costanti è quella che poche persone conoscono. O almeno è la mia conoscenza preferita del C++ che la maggior parte delle persone non conosce.

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

Una caratteristica piacevole 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 consisterebbe nel tradurre l'eccezione in un'altra classe di eccezione e nel riprogrammare, oppure nel tradurre tra eccezioni e gestione del codice di errore basata sul ritorno.

52
vividos

Molti conoscono la metafunzione identity/id, ma esiste un caso d'uso di Nizza per i casi non template: facilita la 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 molto a decifrare le dichiarazioni C++!

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

Una caratteristica abbastanza nascosta è che puoi definire le variabili all'interno di una condizione if e il suo ambito si estenderà solo ai 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 ciò, non è possibile solo 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 troppo sicuro che siano così utili :)

43

Impedire all'operatore virgola di chiamare sovraccarichi 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 fai affidamento su punti di sequenza tra il lato sinistro e destro o vuoi assicurarti che nulla interferisca con il desiderato azione. È qui che entra in gioco void():

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 è void(), che rende la forza del compilatore di utilizzare l'operatore virgola incorporato. Questo può essere utile anche quando si implementano classi di tratti, a volte.

Inizializzazione di array nel costruttore. Ad esempio in una classe se abbiamo una matrice di int come:

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

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

clName::clName() : a()
{
}
28
Poorna

Oooh, posso inventare un elenco di odia da compagnia:

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

Il lato positivo

  • caratteristica nascosta: funzione try blocks. Purtroppo non ne ho trovato alcun uso. Sì, lo so perché l'hanno aggiunto, ma devi ripensare in un costruttore che lo rende inutile.
  • Vale la pena di esaminare attentamente le garanzie STL sulla validità dell'iteratore dopo la modifica del contenitore, che possono consentire di creare loop leggermente più piacevoli.
  • Potenzia - 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 da STL. non è davvero un segreto, ma è un elegante effetto collaterale del sovraccarico e dei modelli dell'operatore.
27
Robert

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

Normalmente, C++ ti proibisce di accedere a membri protetti non statici dell'oggetto di una classe, anche se quella classe è la tua 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 faccia effettivamente riferimento il riferimento. Potrebbe essere un oggetto C, nel qual caso la classe B non ha affari e indizi sui suoi dati. Tale accesso è concesso solo se x è un riferimento a una classe derivata o a una derivata da essa. E potrebbe consentire a un pezzo di codice arbitrario 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 eludere questa protezione! Il punto chiave è che il tipo di puntatore di un membro è associato alla classe che contiene effettivamente detto membro - non alla classe specificata quando si prende il indirizzo. Questo ci consente di eludere 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 ovviamente funziona anche con il 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ù facile con una dichiarazione using nella classe derivata, che rende pubblico il nome del membro e si riferisce 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);
}
27

Funzionalità nascoste:

  1. Le funzioni virtuali pure possono avere un'implementazione. Esempio comune, puro distruttore virtuale.
  2. Se una funzione genera un'eccezione non elencata nelle sue specifiche di eccezione, ma la funzione ha std::bad_exception nelle specifiche dell'eccezione, l'eccezione viene convertita in std::bad_exception e lanciato automaticamente. In questo modo almeno saprai che un bad_exception è stato lanciato. Maggiori informazioni qui .

  3. funzione try blocks

  4. La parola chiave del modello in typedefs non ambigui in un modello di classe. Se il nome di una specializzazione del modello di membro appare dopo un ., ->, o :: operatore, e quel nome ha parametri del modello esplicitamente qualificati, anteporre il nome del modello del membro con il modello di parola chiave. Maggiori informazioni qui .

  5. i valori predefiniti dei parametri di funzione possono essere modificati in fase di esecuzione. Maggiori informazioni qui .

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

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

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

    Maggiori informazioni qui .

  8. Se sono presenti due tipi diversi prima e dopo il : nel ternario (?:) espressione operatore, quindi il tipo risultante dell'espressione è quello 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)
    }
    
26
Sumant

Un'altra caratteristica nascosta è che è possibile chiamare oggetti di classe che possono essere convertiti in puntatori o riferimenti di funzioni. La risoluzione del sovraccarico viene eseguita sul risultato di essi 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".

map::operator[] crea la voce se manca la chiave e restituisce il riferimento al valore della voce predefinito. 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 sorpreso da quanti programmatori C++ non lo sappiano.

24
Constantin

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

20
Jim Hunziker

La definizione delle normali funzioni degli amici 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 del ODR

Dobbiamo quindi assicurarci che i parametri del modello del modello di classe appaiano nel tipo di qualsiasi funzione di amico definita in quel modello (a meno che non vogliamo impedire più di un'istanza di un modello di classe in un determinato file, ma è piuttosto improbabile). Appliciamo questo a una variante 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++: la guida completa /Sezione 8.4

19
Özgür

le funzioni void possono restituire valori vuoti

Poco conosciuto, ma il seguente codice va bene

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

Così come quello seguente dall'aspetto strano

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

Sapendo questo, puoi trarne vantaggio in alcune aree. Un esempio: le funzioni void non possono restituire un valore, ma non puoi anche non restituire nulla, perché possono essere istanziate con non vuoto. Invece di archiviare 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));

istream_iterator

17
Jason Baker

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

Tre di queste cose si uniscono 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 oggetto SomeType 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 un SomeType. Il quinto dichiara allo stesso modo una funzione che accetta un parametro in base al valore di tipo SomeType chiamato u.

14
Eclipse

È possibile creare modelli di 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.

14
Kaz Dragon

La regola del dominio è utile, ma poco conosciuta. Dice che anche se in un percorso non unico attraverso un reticolo di classe base, la ricerca del nome per un membro parzialmente nascosto è unica 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 metodo di allineamento-supporto che individua automaticamente l'allineamento più rigoroso tramite la regola del dominio.

Questo non vale solo per le funzioni virtuali, ma anche per i nomi digitati, i membri statici/non virtuali e qualsiasi altra cosa. L'ho visto usato per implementare tratti sovrascrivibili in meta-programmi.

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 possono essere un'espressione di lancio (che ha il tipo void), indipendentemente dal tipo di altro operando.

In altre parole, si possono scrivere le seguenti espressioni C++ perfettamente valide usando ?: operatore

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

A proposito, il fatto che lanciare un'espressione sia in realtà n'espressione (di tipo void) e non un'istruzione è un'altra caratteristica poco nota del linguaggio C++. Ciò significa, tra l'altro, che il seguente codice è perfettamente valido

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

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

12
AnT

Sbarazzarsi delle dichiarazioni anticipate:

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

Scrivere istruzioni switch con?: Operatori:

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

Fare tutto su una sola riga:

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

Azzeramento delle strutture senza memset:

FStruct s = {0};

Valori angolo e tempo di normalizzazione/avvolgimento:

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

Assegnazione di riferimenti:

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

Ho trovato questo blog una straordinaria risorsa sugli arcani del C++: C++ Truths .

9
Drealmer

Un segreto pericoloso è

Fred* f = new(ram) Fred(); http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.10
f->~Fred();

Il mio segreto preferito che raramente vedo usato:

class A
{
};

struct B
{
  A a;
  operator A&() { return a; }
};

void func(A a) { }

int main()
{
  A a, c;
  B b;
  a=c;
  func(b); //yeah baby
  a=b; //gotta love this
}
8
user34537

Le lezioni locali sono fantastiche:

struct MyAwesomeAbstractClass
{ ... };


template <typename T>
MyAwesomeAbstractClass*
create_awesome(T param)
{
    struct ans : MyAwesomeAbstractClass
    {
        // Make the implementation depend on T
    };

    return new ans(...);
}

abbastanza pulito, poiché non inquina lo spazio dei nomi con definizioni di classe inutili ...

8
Alexandre C.

Una caratteristica nascosta, persino nascosta a sviluppatori GCC , è l'inizializzazione di un membro dell'array usando un valore letterale stringa. Supponiamo di avere una struttura che deve lavorare con un array C e di voler inizializzare il membro dell'array con un contenuto predefinito

struct Person {
  char name[255];
  Person():name("???") { }
};

Funziona e funziona solo con array di caratteri e inizializzatori letterali di stringhe. Non è necessario strcpy!

Un esempio su molti: metaprogrammazione di modelli. Nessuno nel comitato per gli standard intendeva che ci fosse una sublanguage completa di Turing che viene eseguita in fase di compilazione.

La metaprogrammazione dei modelli non è quasi una caratteristica nascosta. È persino nella libreria boost. Vedi MPL . Ma se "quasi nascosto" è abbastanza buono, dai un'occhiata a boost libraries . Contiene molti gadget che non sono facilmente accessibili senza il supporto di una libreria forte.

Un esempio è boost.lambda libreria, il che è interessante poiché C++ non ha funzioni lambda nello standard attuale.

Un altro esempio è Loki , che "fa ampio uso della metaprogrammazione del modello C++ e implementa diversi strumenti comunemente usati: dattilografia, functor, singleton, puntatore intelligente, fabbrica di oggetti, visitatore e metodi multipli". [ Wikipedia ]

6
Markowitch

Non ci sono funzionalità nascoste, ma il linguaggio C++ è molto potente e spesso anche gli sviluppatori di standard non riescono a immaginare a cosa possa servire il C++.

In realtà dalla semplice costruzione del linguaggio puoi scrivere qualcosa di molto potente. Molte di queste cose sono disponibili su www.boost.org come esempi (e http://www.boost.org/doc/libs/1_36_0/doc/html/lambda.html tra questi ).

Per capire come combinare una semplice costruzione del linguaggio a qualcosa di potente, è bene leggere "Modelli C++: la guida completa" di David Vandevoorde, Nicolai M. Josuttis e un libro davvero magico "Modern C++ Design ..." di Andrei Alexandresc .

E infine, è difficile imparare il C++, dovresti provare a riempirlo;)

5
sergtk

Mi sembra che solo poche persone conoscano spazi dei nomi senza nome:

namespace {
  // Classes, functions, and objects here.
}

Gli spazi dei nomi senza nome si comportano come se fossero sostituiti da:

namespace __unique_{ /* empty body */ }
using namespace __unique_name__;
namespace __unique_{
  // original namespace body
}

".. dove tutte le occorrenze di [questo nome univoco] in un'unità di traduzione sono sostituite dallo stesso identificatore e questo identificatore differisce da tutti gli altri identificatori nell'intero programma." [C++ 03, 7.3.1.1/1]

4
vobject
4
Özgür

Non sono sicuro di nascosto, ma ci sono alcuni interessante'trucchi' che probabilmente non sono ovvi dalla sola lettura delle specifiche.

3
dbrien

Esistono molti "comportamenti indefiniti". Puoi imparare come evitare che leggano buoni libri e leggano gli standard.

3
ugasoft

La maggior parte degli sviluppatori C++ ignora la potenza della metaprogrammazione dei modelli. Dai un'occhiata Loki Libary . Implementa diversi strumenti avanzati come dattilografo, functor, singleton, puntatore intelligente, fabbrica di oggetti, visitatore e multimetodi usando la metaprogrammazione del modello ampiamente (da wikipedia ). Per la maggior parte, potresti considerarli come funzionalità c ++ "nascosta".

3
Sridhar Iyer

Da Verità C++ .

Definire funzioni con firme identiche nello stesso ambito, quindi questo è legale:

template<class T> // (a) a base template
void f(T) {
  std::cout << "f(T)\n";
}

template<>
void f<>(int*) { // (b) an explicit specialization
  std::cout << "f(int *) specilization\n";
}

template<class T> // (c) another, overloads (a)
void f(T*) {
  std::cout << "f(T *)\n";
}

template<>
void f<>(int*) { // (d) another identical explicit specialization
  std::cout << "f(int *) another specilization\n";
}
3
Özgür
  • puntatori ai metodi di classe
  • La parola chiave "typename"
3
shoosh
3
sdcvvc

main () non ha bisogno di un valore di ritorno:

int main(){}

è il programma C++ valido più breve.

2
Jeffrey Faust

Prestare attenzione alla differenza tra il puntatore a funzione libera e le inizializzazioni del puntatore a funzione membro:

funzione membro:

struct S
{
 void func(){};
};
int main(){
void (S::*pmf)()=&S::func;//  & is mandatory
}

e funzione gratuita:

void func(int){}
int main(){
void (*pf)(int)=func; // & is unnecessary it can be &func as well; 
}

Grazie a questo ridondante &, è possibile aggiungere manipolatori di flusso, che sono funzioni gratuite, in catena senza di esso:

cout<<hex<<56; //otherwise you would have to write cout<<&hex<<56, not neat.
2
Özgür
  1. map::insert(std::pair(key, value)); non sovrascrive se il valore chiave esiste già.

  2. Puoi istanziare una classe subito dopo la sua definizione: (potrei aggiungere che questa funzione mi ha dato centinaia di errori di compilazione a causa del punto e virgola mancante, e non ho mai visto nessuno usarlo nelle classi)

    class MyClass {public: /* code */} myClass;
    
2
Viktor Sehr

Ci sono tonnellate di costrutti "difficili" in C++. Esse vanno da implementazioni "semplici" di classi sigillate/finali usando l'eredità virtuale. E arriva a costrutti meta "complessi" di meta-programmazione come MPL [~ # ~]) DI BOOST ( tutorial ). Le possibilità di spararti al piede sono infinite, ma se tenute sotto controllo (cioè programmatori esperti), offrono la migliore flessibilità in termini di manutenibilità e prestazioni.

1
Amir

Le chiavi di classe class e struct sono quasi identiche. La differenza principale è che le classi predefiniscono l'accesso privato per membri e basi, mentre le strutture predefinite sono pubbliche:

// this is completely valid C++:
class A;
struct A { virtual ~A() = 0; };
class B : public A { public: virtual ~B(); };

// means the exact same as:
struct A;
class A { public: virtual ~A() = 0; };
struct B : A { virtual ~B(); };

// you can't even tell the difference from other code whether 'struct'
// or 'class' was used for A and B

I sindacati possono anche avere membri e metodi e accedere automaticamente al pubblico in modo simile alle strutture.

1
a_m0d

Idioma di conversione indiretta :

Supponiamo che tu stia progettando una classe di puntatori intelligenti. Oltre a sovraccaricare gli operatori * e ->, una classe di puntatore intelligente di solito definisce un operatore di conversione da bool:

template <class T>
class Ptr
{
public:
 operator bool() const
 {
  return (rawptr ? true: false);
 }
//..more stuff
private:
 T * rawptr;
};

La conversione in bool consente ai client di utilizzare i puntatori intelligenti nelle espressioni che richiedono operandi bool:

Ptr<int> ptr(new int);
if(ptr ) //calls operator bool()
 cout<<"int value is: "<<*ptr <<endl;
else
 cout<<"empty"<<endl;

Inoltre, la conversione implicita in bool è richiesta in dichiarazioni condizionali come:

if (shared_ptr<X> px = dynamic_pointer_cast<X>(py))
{
 //we get here only of px isn't empty
} 

Purtroppo, questa conversione automatica apre le porte a sorprese indesiderate:

Ptr <int> p1;
Ptr <double> p2;

//surprise #1
cout<<"p1 + p2 = "<< p1+p2 <<endl; 
//prints 0, 1, or 2, although there isn't an overloaded operator+()

Ptr <File> pf;
Ptr <Query> pq; // Query and File are unrelated 

//surprise #2
if(pf==pq) //compares bool values, not pointers! 

Soluzione: utilizzare il linguaggio "conversione indiretta", mediante una conversione dal puntatore al membro dati [pMember] in bool in modo che ci sia solo 1 conversione implicita, che impedirà il comportamento imprevisto di cui sopra: pMember-> bool piuttosto che bool-> qualcosa altro.

1
Özgür

Se l'operatore delete () accetta l'argomento size oltre a * void, ciò significa che sarà, altamente, una classe base. Tale argomento sulla dimensione rende possibile il controllo della dimensione dei tipi al fine di distruggere quello corretto. Ecco cosa dice Stephen Dewhurst a questo proposito:

Si noti inoltre che abbiamo utilizzato una versione a due argomenti dell'operatore delete anziché la solita versione a un argomento. Questa versione a due argomenti è un'altra versione "normale" dell'eliminazione dell'operatore membro spesso utilizzata dalle classi di base che si aspettano che le classi derivate ereditino l'implementazione dell'eliminazione dell'operatore. Il secondo argomento conterrà le dimensioni dell'oggetto da eliminare, informazioni che sono spesso utili nell'implementazione della gestione della memoria personalizzata.

1
Özgür

Trovo che i modelli ricorsivi siano abbastanza interessanti:

template<class int>
class foo;

template
class foo<0> {
    int* get<0>() { return array; }
    int* array;  
};

template<class int>
class foo<i> : public foo<i-1> {
    int* get<i>() { return array + 1; }  
};

L'ho usato per generare una classe con 10-15 funzioni che restituiscono i puntatori in varie parti di un array, poiché un'API che ho usato richiedeva un puntatore a funzione per ciascun valore.

Cioè programmare il compilatore per generare un sacco di funzioni, tramite ricorsione. Facile come una torta. :)

1
Macke

Il mio preferito (per il momento) è la mancanza di semantica in un'affermazione come A = B = C. Ciò che il valore di A è sostanzialmente indeterminato.

Pensa a questo:

class clC
{
public:
   clC& operator=(const clC& other)
   {
      //do some assignment stuff
      return copy(other);
   }
   virtual clC& copy(const clC& other);
}

class clB : public clC
{
public:
  clB() : m_copy()
  {
  }

  clC& copy(const clC& other)
  {
    return m_copy;
  }

private:
  class clInnerB : public clC
  {
  }
  clInnerB m_copy;
}

ora A potrebbe essere di un tipo inaccessibile a qualsiasi altro che oggetti di tipo clB e avere un valore non correlato a C.

0
Rune FS

Aggiunta di vincoli ai modelli.

0
Özgür

Puntatori membri e operatore puntatore membro -> *

#include <stdio.h>
struct A { int d; int e() { return d; } };
int main() {
    A* a = new A();
    a->d = 8;
    printf("%d %d\n", a ->* &A::d, (a ->* &A::e)() );
    return 0;
}

Per i metodi (a -> * & A :: e) () è un po 'come Function.call () da JavaScript

var f = A.e
f.call(a) 

Per i membri è un po 'come accedere con l'operatore []

a['d']
0
Kamil Szot

È possibile visualizzare tutte le macro predefinite tramite opzioni della riga di comando con alcuni compilatori. Funziona con gcc e icc (compilatore C++ di Intel):

$ touch empty.cpp
$ g++ -E -dM empty.cpp | sort >gxx-macros.txt
$ icc -E -dM empty.cpp | sort >icx-macros.txt
$ touch empty.c
$ gcc -E -dM empty.c | sort >gcc-macros.txt
$ icc -E -dM empty.c | sort >icc-macros.txt

Per MSVC sono elencati in un posto singolo . Potrebbero essere documentati in un unico posto anche per gli altri, ma con i comandi sopra puoi chiaramente vedere cosa è e non è definito ed esattamente cosa vengono utilizzati i valori, dopo aver applicato tutte le altre opzioni della riga di comando.

Confronta (dopo l'ordinamento):

 $ diff gxx-macros.txt icx-macros.txt
 $ diff gxx-macros.txt gcc-macros.txt
 $ diff icx-macros.txt icc-macros.txt
0
Roger Pate
class Empty {};

namespace std {
  // #1 specializing from std namespace is okay under certain circumstances
  template<>
  void swap<Empty>(Empty&, Empty&) {} 
}

/* #2 The following function has no arguments. 
   There is no 'unknown argument list' as we do
   in C.
*/
void my_function() { 
  cout << "whoa! an error\n"; // #3 using can be scoped, as it is in main below
  // and this doesn't affect things outside of that scope
}

int main() {
  using namespace std; /* #4 you can use using in function scopes */
  cout << sizeof(Empty) << "\n"; /* #5 sizeof(Empty) is never 0 */
  /* #6 falling off of main without an explicit return means "return 0;" */
}
0
dirkgently