2013-04-30 8 views
7

consideri l'oggetto:Const correttezza con oggetti contenenti shared_ptr

class Obj 
{ 
    public: 
     Obj() : val(new int(1)) {} 
     int& get() {return *val;} 
     const int& get() const {return *val;} 

    private: 
     std::shared_ptr<int> val; 
}; 

Come previsto, quando l'oggetto è costruito e copie sono fatti tutti possono modificare lo stesso valore attraverso la shared_ptr esposto da Ob.

Obj nonconst1; 
    Obj nonconst2(nonconst1); 
    nonconst2.get() = 2; 
    cout << nonconst1.get() << ", " << nonconst2.get() << endl; 

E 'anche possibile copiare-costruire un oggetto const Obj da uno dei non const, che sembra fare la cosa giusta in quanto permette di leggere ma non scrivere per il valore - come previsto i seguenti risultati di codice in un errore di compilazione:

const Obj const1(nonconst1); 
    const1.get() = 3; 

Tuttavia è possibile copiare-costruire un non-const Obj dal const uno, che poi non permettono il valore da modificare.

Obj nonconst3(const1); 
    nonconst3.get() = 3; 

Per me questo non mi sembra corretto.

C'è un modo per impedire questo comportamento, pur consentendo al costruttore di copiare di funzionare? Nel mio vero caso d'uso, voglio ancora che i contenitori std di Obj siano possibili.

+0

Forse, dovresti definire il copy-constructor che prende il riferimento non const per evitare di fare copia di oggetti 'const'? – Nawaz

+0

@Nawaz, che funziona (non mi ero reso conto che era possibile usare un ref non-const in un costruttore di copie). Sfortunatamente mi piacerebbe anche che const Obj fosse in grado di copiare altri costrutti Const obj ma il tentativo di 'Obj (const Obj & o) const' non viene compilato – tangobravo

risposta

1

No, non c'è, a meno che non si desideri memorizzare un shared_ptr<const int>, nel qual caso nessuno può accedervi come non-const.

0

Implementare manualmente un costruttore di copia di Obj che dovrebbe quindi copiare il contenuto del puntatore condiviso. Ciò evita di modificare il contenuto di const1 tramite nonconst3, poiché punta a istanze int differenti.

Tuttavia, si vuole evitare di copie profonde di non const istanze di Obj (dove non è un problema e destinato ad riutilizzare un vecchio puntatore in comune). Per questo, è necessario fornire sia const e non const costruttori di copia e copiare solo nel const uno:

class Obj 
{ 
    public: 
    //... 
    Obj(Obj &o) : val(o.val) {}       // not a deep copy 
    Obj(const Obj &o) : val(std::make_shared(o.get())) {} // deep copy 
    //... 
} 
2

"A me questo non si sente const-corretto" ma è: si sta semplicemente invocare un metodo non const get su un non const Obj. Niente di male in questo.

Se si ha realmente bisogno il comportamento che stai dopo, si potrebbe usare qualcosa come un proxy per const Obj ma poi i client devono essere in grado di gestirlo, naturalmente:

class Obj 
{ 
    //... 
    //original class definition goes here 
    //... 
    friend class ConstObj; 
}; 

class ConstObj 
{ 
    public: 
    ConstObj(const Obj& v) : val(v.val) {} 
    const int& get() const { return *val; } 

    private: 
    std::shared_ptr<int> val; 
}; 

//usage: 
class WorkingWithObj 
{ 
public: 
    WorkingWithObj(); 
    Obj DoSomethingYieldingNonConstObj(); 
    ConstObj DoSomethingYieldingConstObj(); 
}; 

WorkingWithObj w; 
Obj nonconst(w.DoSomethingYieldingNonConstObj()); 
nonconst.get() = 3; 

ConstObj veryconst(nonconst); 
veryconst.get() = 3; //compiler error 

ConstObj alsoconst(w.DoSomethingYieldingConstObj()); 
alsoconst.get() = 3; //compiler error 
+1

Puoi spiegare la tua risposta un po 'di più? Non vedo nulla in "Obj" e il modello di utilizzo? – Nawaz

+0

@Nawaz ha aggiunto alcuni commenti/esempi di utilizzo. Anche se devo dire che probabilmente non avrei mai usato costrutti come questo nel codice reale .. – stijn

0

No, là isn 't ... Ma è possibile utilizzare il puntatore , deep-copy, quando è possibile scrivere su value (in getter non-const).

Oppure, è possibile scrivere due copy-ctors (per ref fare una copia superficiale, per cref fare una copia profonda).

A(A& obj) : pointer(obj.pointer) {} 
    A(const A& obj) : pointer(new int(*obj.pointer)) {} 
1

Ciò non interrompe la correttezza const. L'oggetto intero puntato da val è un oggetto distinto, che non è di proprietà esclusiva dell'oggetto originale. La modifica del suo valore non influisce sullo stato degli oggetti Obj.

1

Is there a way to prevent this behaviour, whilst still allowing the copy constructor to work? In my real use case, I still want std containers of Obj to be possible.

È possibile specificare un costruttore di copia diverso per la copia da un oggetto const - che significa che è possibile per esempio evitare di copiare il puntatore condiviso e creare invece l'oggetto non-const con un puntatore NULL oppure eseguire una copia profonda del numero puntato. Sarei piuttosto cauto nel fare questo genere di cose - è strano ottenere comportamenti diversi a seconda della costanza della variabile copiata - temo che renderebbe molto difficile ragionare sul comportamento del tuo programma. Tuttavia, è necessario scegliere un comportamento o accettare il comportamento corrente in quanto lo std::vector<> creerà delle copie a volte - non è possibile semplicemente lasciarlo indefinito.