2009-10-31 13 views
6

Ho deciso di familiarizzare con il mio linguaggio di programmazione preferito, ma lo solo la lettura dello standard è noioso.Quali sono gli elementi più sorprendenti dello standard C++?

Quali sono gli elementi più sorprendenti, controintuitivi o semplicemente bizzarri del C++? Cosa ti ha scioccato abbastanza da correre al tuo compilatore più vicino per verificare se è vero?

Accetterò la prima risposta che non crederò nemmeno dopo L'ho provato. :)

+7

Dovrebbe essere comunità wiki. – Brian

+1

Vedere anche http://stackoverflow.com/questions/75538/hidden-feature-of-c –

+0

Grazie. Non mi è venuto in mente di cercare "caratteristiche nascoste". –

risposta

8

L'ordine dei vari dichiaratori è in realtà non ordinato:

volatile long int const long extern unsigned x; 

è uguale

extern const volatile unsigned long long int x; 
+2

È davvero così sorprendente? Ti va di spiegare perché sei rimasto sorpreso? – akent

+9

Io, per esempio, non sapevo che si potesse separare la parte "long long int". –

+0

In realtà, è illegale per gli specificatori di memorizzazione come "extern" apparire all'interno dello specificatore di tipo. Devono apparire prima o dopo. Il tuo primo esempio non è C++ valido, ma lo sarebbe se scambiassi "extern" e "unsigned". –

5

Considerando quanto inesorabile C++ è solitamente, ho trovato alquanto sorprendente che lo standard permette effettivamente a delete puntatori nulli.

+5

Trovo sorprendente il numero di persone che programma in C++ che non sanno che è possibile eliminare i puntatori nulli. – bk1e

+0

Ho imparato C prima di passare a C++, quindi ho imparato a non fare _anything_ con un puntatore NULL. Il fatto che C++ definisca un no-op per 'delete NULL;' potrebbe essere utile, ma può anche nascondere bug. –

+3

Non c'è da stupirsi se ci si rende conto che in C 'free (NULL)' è ben definito. – MSalters

9

Un'altra risposta che potrei aggiungere sarebbe il qualificatore throw(). Per esempio:

void dosomething() throw() 
{ 
    // .... 
} 

void doSomethingElse() throw(std::exception) 
{ 
    // .... 
} 

Intuitivamente, questo mi sembra un contratto con il compilatore, indicando che questa funzione non è permesso di gettare le eventuali eccezioni ad eccezione di quelli elencati. Ma in realtà, questo non fa nulla al momento della compilazione. Piuttosto si tratta di un meccanismo in fase di esecuzione e non impedirà alla funzione di generare effettivamente un'eccezione. Ancora peggio, se viene lanciata un'eccezione non elencata, termina l'applicazione con una chiamata a std::terminate().

+4

Herb Sutter spiega in modo molto dettagliato perché non dovresti mai scrivere le specifiche delle eccezioni: http://www.gotw.ca/publications/mill22.htm –

+0

Questo è esattamente il motivo per cui utilizziamo la frase di commento commentata, solo a scopo di documentazione ... piuttosto un peccato:/ –

0

Non è molto noto che un iniziatore di array può saltare in indice come nella definizione di enumerazione.

// initializing an array of int 
int a[ 7] = { [5]=1, [2]=3, 2}; 
// resulting in 
int a[ 7] = { 0, 0, 3, 2, 0, 1, 0}; 

// initializing an array of struct 
struct { int x,y; } ar[ 4] = { [1].x=23, [3].y=34, [1].y=-1, [1].x=12}; 
// resulting in 
struct { int x,y; } ar[ 4] = { { 0, 0}, { 12, -1}, { 0, 0}, { 0, 34}}; 

// interesting usage 
char forbidden[ 256] = { ['a']=1, ['e']=1, ['i']=1, ['o']=1, ['u']=1}; 

La maggior parte di these vale anche per C++.

+5

-1, questa è un'estensione gcc. La domanda menziona lo standard in base al quale il frammento di cui sopra è mal formato. – avakar

+0

sì, questo è specifico per gcc. fantastico come gcc, non fa parte dello standard. –

10

ho trovato un po 'sorprendente che

class aclass 
{ 
public: 
int a; 
}; 

some_function(aclass()); 

dovrà a inizializzati per 0 in some_function, mentre

aclass ac; 
some_function(ac); 

lascerà unitinitalized. Se si definisce esplicitamente il costruttore di default di aclass:

class aclass 
{ 
public: 
aclass(): a() {} 
int a; 
}; 

poi aclass ac; sarà anche inizializzare a-0.

+0

cosa? cura di spiegare perché è diverso? Inizializza lo spazio dello stack spinto per il piacere di una chiamata? –

+6

Utilizzando 'aclass()' come parametro, il compilatore compila un costrutto predefinito che inizializza tutto al valore predefinito. Ma quando si dichiara una variabile, non viene chiamato alcun costruttore, a meno che non si sia definito un costruttore predefinito. – henle

+0

hmm, è vero, l'ho provato: P. +1 per qualcosa che non sapevo. –

2

Gli oggetti cambiano tipo durante la costruzione.

In particolare, quando si chiama una funzione virtuale da un costruttore, si non si chiama chiamando l'override più derivato. Piuttosto, stai chiamando l'implementazione dalla classe che stai creando attualmente.Se tale implementazione risulta essere 0 (pura funzione virtuale), il programma si arresta in modo anomalo in fase di runtime.

Un esempio semi-mondo reale:

class AbstractBase { 
    public: 
    AbstractBase() { 
     log << "Creating " << className() << endl; 
    } 
    protected: 
    virtual string className() const = 0; 
} 

class ConcreteGuy { 
    protected: 
    virtual string className() const { return "ConcreteGuy"; } 
} 

Al momento la costruzione di un oggetto ConcreteGuy, il programma terminerà con un messaggio di "chiamata puro virtuale funzione" errore.

Ecco perché chiamare le funzioni virtuali dai costruttori è considerato malvagio.

+3

non è così sorprendente, quando costruisci un ConcreteGuy (supponendo che sia derivato da AbstractBase) mentre la classe base viene costruita per prima, non ha una classe ConcreteGuy da chiamare fino al completamento del costruttore. – gbjbaanb

+1

Ha senso, sono d'accordo. Ma ancora mi ha causato un po 'di problemi con le teste quando mi sono imbattuto per la prima volta in questo. Soprattutto perché Java e C# si comportano in modo diverso, consentendo di chiamare metodi su oggetti che non sono completamente costruiti. – Thomas

+1

@gbjbaanb (che nome!): Non proprio, il compilatore corrisponderà alla chiamata di costruzione con i costruttori di tipi più derivati ​​e inizierà ad eseguire quel costruttore. Ora, l'ordine di esecuzione è prima l'elenco di inizializzazione, quindi il corpo del costruttore e lo standard indica che l'inizializzazione inizia a eseguire la prima classe di base della classe corrente. L'algoritmo implica che il primo tipo completamente costruito (sia l'elenco di inizializzazione sia il corpo del costruttore in competizione) sia il tipo meno derivato (con una sua definizione) ... –

0

In C++ dichiarazioni valutare a qualcosa ...

int main() 
{ 
    "This is a valid C++ program!" 
    "I will list the first five primes:"; 
    2; 
    3; 
    5; 
    7; 
    11; 
} 
+0

Non è un programma valido senza una dichiarazione di ritorno. –

+4

In realtà, in C++, 'main' non ha bisogno di restituire esplicitamente un valore. –

+1

@Jurily Lo è! controlla gli standard :) – AraK

Problemi correlati