Se la modifica di un oggetto const è un comportamento indefinito allora come costruttori e distruttori operano con accesso in scrittura?
-
20-09-2019 - |
Domanda
C ++ standard dice che modifica di un oggetto const
originariamente dichiarato è un comportamento indefinito. Ma allora come fanno costruttori e distruttori operano?
class Class {
public:
Class() { Change(); }
~Class() { Change(); }
void Change() { data = 0; }
private:
int data;
};
//later:
const Class object;
//object.Change(); - won't compile
const_cast<Class&>( object ).Change();// compiles, but it's undefined behavior
Voglio dire qui il costruttore e distruttore fanno esattamente la stessa cosa come il codice chiamante, ma essi sono autorizzati a modificare l'oggetto e il chiamante non è permesso -. Si imbatte in un comportamento indefinito
Come si suppone di lavorare sotto un'implementazione e secondo lo standard?
Soluzione
Lo standard consente esplicitamente costruttori e distruttori a che fare con oggetti const
. da 12,1 / 4 "Costruttori":
Un costruttore può essere richiamato per un
const
,volatile
o oggettoconst volatile
. ...const
evolatile
semantica (7.1.5.1) non sono applicate su un oggetto in costruzione. Tale semantica diventano attive solo dopo la costruzione per l'oggetto più derivato (1.8) termina.
E 12.4 / 2 "Distruttori":
Un distruttore può essere richiamato per un
const
,volatile
o oggettoconst volatile
. ...const
evolatile
semantica (7.1.5.1) non sono applicati su un oggetto sotto distruzione. Tale semantica smettono di essere in vigore una volta che il distruttore per l'oggetto più derivato (1.8) inizia.
Come sfondo, Stroustrup dice in "Progettazione ed evoluzione del C ++" (13.3.2 Affinamento del Defintion di const
):
Per assicurare che alcuni, ma non tutti, gli oggetti
const
potrebbero essere immessi memoria di sola lettura (ROM), ho adottato la regola che qualsiasi oggetto che ha un costruttore (cioè inizializzazione runtime richiesta) non può essere posto in ROM, ma altri oggetticonst
possibile....
Un
const
dichiarata oggetto è considerato immutabile dal completamento del costruttore fino all'inizio del suo distruttore. Il risultato di una scrittura all'oggetto tra questi punti sono ritenute indefinito.Quando originariamente progettazione
const
, mi ricordo sostenendo che ilconst
ideale sarebbe un oggetto che è scrivibile fino a quando il costruttore era corsa, poi diventa di sola lettura da qualche magia di hardware, e, infine, con l'entrata in distruttore ridiventa scrivibile. Si potrebbe immaginare un'architettura etichettato che in realtà ha funzionato in questo modo. Tale implementazione potrebbe causare un errore di run-time se qualcuno potesse scrivere in unconst
oggetto definito. D'altra parte, qualcuno potrebbe scrivere a unconst
oggetto non definito che era stato passato come riferimentoconst
o un puntatore. In entrambi i casi, l'utente avrebbe dovuto gettare viaconst
prima. L'implicazione di questa visione è che gettando viaconst
per un oggetto che è stato originariamente definitoconst
e poi la scrittura è nella migliore delle ipotesi non definita, mentre facendo lo stesso ad un oggetto che non è stato originariamente definitoconst
è legale e ben definito.Si noti che con questa raffinatezza delle regole, il significato di
const
non dipende dal fatto che un tipo ha un costruttore o no; in linea di principio, lo fanno tutti. Qualsiasi oggettoconst
dichiarato ora può essere messo in ROM, essere collocato in segmenti di codice, protetto dal controllo di accesso, ecc, per garantire che non muta che riceve il suo valore iniziale. Tale protezione non è necessario, però, perché i sistemi attuali non possono in generale proteggere ogniconst
da ogni forma di corruzione.
Altri suggerimenti
Per approfondire quello che Jerry Coffin ha detto:. Lo standard rende l'accesso un oggetto const non definito, solo se si verifica che l'accesso durante la vita di un oggetto
7.1.5.1/4:
Se non fosse che ogni membro della classe dichiarata mutabile (7.1.1) può essere modificato, qualsiasi tentativo di modificare un oggetto const durante la sua vita (3,8) si traduce in un comportamento indefinito.
Il ciclo di vita dell'oggetto inizia solo dopo che il costruttore ha terminato.
3.8 / 1:
La durata di un oggetto di tipo T inizia quando:
- stoccaggio con il corretto allineamento e la dimensione per il tipo T viene ottenuta, e
- se T è un tipo di classe con un costruttore non banale (12.1), la chiamata al costruttore ha completato.
La norma in realtà non dice molto su come l'implementazione rende il lavoro, ma l'idea di base è piuttosto semplice: il const
applica all'oggetto , non (necessariamente) per la memoria in cui è memorizzato l'oggetto. Dal momento che il ctor è parte di ciò che crea l'oggetto, non è davvero un oggetto fino a quando (a volte poco dopo) i rendimenti ctor. Allo stesso modo, dal momento che la dtor partecipa a distruggere l'oggetto, non è più in realtà che operano su un oggetto completo sia.
Ecco un modo che ignorare lo standard potrebbe portare a comportamenti scorretti. Si consideri una situazione come questa:
class Value
{
int value;
public:
value(int initial_value = 0)
: value(initial_value)
{
}
void set(int new_value)
{
value = new_value;
}
int get() const
{
return value;
}
}
void cheat(const Value &v);
int doit()
{
const Value v(5);
cheat(v);
return v.get();
}
Se ottimizzata, il compilatore sa che v è const così potrebbe sostituire la chiamata a v.get()
con 5
.
Ma diciamo che in un'unità di traduzione diverso, avete definito cheat()
in questo modo:
void cheat(const Value &cv)
{
Value &v = const_cast<Value &>(cv);
v.set(v.get() + 2);
}
Così, mentre sulla maggior parte delle piattaforme di questo verrà eseguito, il comportamento potrebbe cambiare a seconda di cosa l'ottimizzatore fa.
constness per un tipo definito dall'utente è diverso constness per un tipo incorporato. Constness quando utilizzato con tipi definiti dall'utente è detto di essere "constness logico". Il compilatore impone che solo funzioni membro dichiarate "const" può essere chiamato su un oggetto const (o puntatore, o di riferimento). Il compilatore non può allocare l'oggetto in una certa zona memoria a sola lettura, perché non-const funzioni utente deve essere in grado di modificare lo stato dell'oggetto (e anche const funzioni membro deve essere in grado di mutable
quando viene dichiarata una variabile membro).
Per tipi built-in, credo che il compilatore è permesso di allocare l'oggetto in memoria di sola lettura (se la piattaforma supporta una cosa del genere). Così gettar via il const e modificando la variabile potrebbe causare un errore di protezione della memoria in fase di esecuzione.