2010-08-17 2 views
11
class Foo { 
public: 
static const int kType = 42; 
}; 

void Func() { 
Foo *bar = NULL; 
int x = bar->kType; 
putc(x, stderr); 
} 

Questo comportamento definito? Ho letto lo standard C++ ma non ho trovato nulla sull'accesso a un valore const statico come questo ... Ho esaminato l'assembly prodotto da GCC 4.2, Clang ++ e Visual Studio 2010 e nessuno di questi esegue un dereferenziamento del NULL puntatore, ma mi piacerebbe esserne sicuro.C++ accesso const const tramite un puntatore NULL

+1

La maggior parte dei compilatori dovrebbe fornire un avviso sull'accesso di membri static/const tramite un puntatore di istanza. – cHao

risposta

10

È possibile utilizzare un puntatore (o un'altra espressione) per accedere a un membro statico; tuttavia, farlo attraverso un puntatore NULL sfortunatamente è un comportamento ufficialmente indefinito. Da 9.4/2 "membri statici":

Un membro statico s di classe X può essere cui utilizzando il qualificato-id espressione X :: s; non è necessario utilizzare la sintassi di accesso del membro della classe (5.2.5) per fare riferimento a un membro statico. Un membro statico può fare riferimento a utilizzando la sintassi di accesso del membro della classe, in , in questo caso l'espressione dell'oggetto è valutata .

sull'esempio che segue:

class process { 
public: 
    static void reschedule(); 
}; 

process& g(); 

void f() 
{ 
    process::reschedule(); // OK: no object necessary 
    g().reschedule();  // g() is called 
} 

L'intento è quello di permettere di garantire che le funzioni saranno chiamati in questo scenario.

+4

Stavo per dividere un capello e sottolineare che "valutare l'espressione dell'oggetto" non implica necessariamente * il dereferenziamento del puntatore * - potrebbe semplicemente significare trovare il valore del puntatore - ma guardando §5.2.5, appare che * fa *, perché la "espressione oggetto" in questo caso è definita come "' * bar' ", non solo" 'bar'". Quindi hai ragione. – zwol

+0

Non è stato oggetto di dibattito? La nozione di valutare un oggetto, come semplicemente nominandolo, è piuttosto zen. D'altra parte, l'esempio sotto quel testo mostra chiaramente una chiamata di funzione. Chiaramente significano che l'espressione prima del "." o "->" è valutato. – Potatoswatter

+2

@Potatoswatter: Credo che questa mia interpretazione sia corretta, ma certamente non mi considero un'autorità. Mentre penso che la probabilità di chiamare una funzione membro statica attraverso un puntatore NULL opportunamente digitato funzioni come ci si potrebbe aspettare, è abbastanza facile fare in modo che la chiamata venga fatta usando il nome del tipo piuttosto che un puntatore (anche se penso per i template potrebbe non essere sempre vero), quindi potresti star meglio senza semplicemente usare i puntatori per questo. –

3

ritengo che il valore effettivo del tipo non viene usato affatto quando chiamate

bar->kType 

dal kType è statico, e la barra è di tipo Foo è la stessa chiamata

Foo::kType 

che dovresti davvero fare comunque per chiarezza.

La chiamata bar->kType fornisce un avvertimento del compilatore sulla maggior parte delle piattaforme per questo motivo.

+0

+1 "che dovresti fare comunque per chiarezza" – leedm777

+0

Corretti, i membri statici sono archiviati indipendentemente dalle istanze dell'oggetto, quindi l'oggetto stesso non viene mai dereferenziato. – casablanca

+0

@casablanca: questo è un dettaglio di implementazione. Se hai la prova documentata che questo è il comportamento della tua particolare versione del compilatore, allora fantastico! Altrimenti, non farlo. Certamente, in termini di domande sulla macchina astratta chiamata C++, questa non è una risposta "corretta", temo. –

1

A parte la questione su come accedere attraverso il puntatore NULL, c'è un'altra questione sottile nel codice di

$ 9.4.2/2 - "La dichiarazione di un membro di dati statici nella sua definizione di classe non è una definizione e può essere di un tipo incompleto diverso da qualifiche qualificate per cv. La definizione per un membro di dati statici deve apparire in un ambito di namespace che racchiude la definizione di classe del membro. "

$ 9.4.2/4- "Se un membro dati statici è di tipo const integrale o const enumeration, la sua dichiarazione nella definizione di classe può specificare un inizializzatore costante che deve essere un'espressione costante integrale (5.19). caso, il membro può apparire in espressioni costanti integrali.Il membro deve ancora essere definito in un ambito namespace se viene utilizzato nel programma e la definizione dell'ambito dello spazio dei nomi non deve contenere un inizializzatore. "

class Foo { 
public: 
static const int kType = 42; 
}; 

int const Foo::kType; 

void Func() { 
Foo *bar = NULL; 
int x = bar->kType; 
putc(x, stderr); 
} 

Quindi, ancora un motivo in più per UB nel codice OP.

1

Anche se ha funzionato è un codice terribile.

Nella programmazione seria, il codice non è solo per te stesso, ma anche per gli altri che manterranno il tuo codice. Giocare trucchi del genere deve essere evitato, perché rispetti i tuoi colleghi.

Una conseguenza di questo codice: se il puntatore è NULL oppure no non è nemmeno in discussione, ma implica che questo membro kType non può essere un semplice membro non statico della classe. A volte le classi sono grandi (anche questo è male) e non è sempre possibile ricontrollare la definizione di ogni variabile.

Essere rigorosi. E chiamare tutti i membri statici solo in questo modo:

Foo::kType 

Un'altra possibilità è quella di seguire una convenzione di codifica che fa sapere che il membro è statico, ad esempio, un prefisso s_ per tutte le classi i membri statici:

Foo::s_kType 
-1

C'è una regola più alta per così dire che in pratica dice: non pensate nemmeno di compilare cose che non sono probabilmente usate. La programmazione avanzata dei template dipende molto da questo, quindi anche se potrebbe essere un po 'grigio-zonista quando un compilatore vede chiaramente che il risultato di un costrutto non viene usato, lo eliminerà. Soprattutto quando è provabilmente sicuro come in questo caso.

Si consiglia di provare alcune varianti, se lo si desidera, ad esempio rendere il puntatore un parametro di una funzione, risultato di una funzione, lasciare un puntatore non inizializzato (possibilità migliore di attivare il reclamo del compilatore), eseguire un cast immediato di 0 (le migliori possibilità di essere senza conintesti).

+0

È sciocco. Tutti i tipi di ottimizzazioni potrebbero comportare il mancato funzionamento della chiamata di funzione o [altre cose pazzesche] (http://blogs.msdn.com/b/oldnewthing/archive/2014/06/27/10537746.aspx). Sono serio. Questo può succedere nella pratica. –