2011-09-12 22 views
12

Ultimamente ho imparato i puntatori gestiti e mi sono imbattuto nel seguente scenario.C++ shared_ptr di stack object

Sto implementando una classe model/controller per una vista di gioco. La mia opinione, renderà le cose nel modello. Abbastanza diretto. Nella mia funzione principale, ho un'istanza di tutti e tre in questo modo:

RenderModel m; 
m.AddItem(rect); // rect gets added just fine, it's an "entity" derivee 
RenderView v; 
v.SetModel(m); 

il mio render vista della classe è abbastanza semplice:

class RenderView 
{ 
public: 
explicit RenderView(); 
~RenderView(); 

void Update(); 

void SetModel(RenderModel& model); 

private: 
// disable 
RenderView(const RenderView& other); 
RenderView& operator=(const RenderView& other); 

// private members 
boost::scoped_ptr<RenderModel> _model; 
}; 

L'implementazione per setview è abbastanza standard:

void RenderView::SetModel(RenderModel& model) 
{ 
    _model.reset(&model); 
} 

Il la chiave per questo è, la vista memorizza un modello in un puntatore intelligente. Tuttavia, in main, il modello è stato assegnato in pila. Quando il programma termina, la memoria viene eliminata due volte. Questo ha senso. La mia attuale comprensione mi dice che tutto ciò che viene memorizzato in uno smart_ptr (di qualsiasi tipo) non dovrebbe essere stato allocato nello stack.

Dopo tutte le impostazioni di cui sopra, la mia domanda è semplice: come faccio a dettare che un parametro non è stato allocato nello stack? Accettare un puntatore intelligente come parametro è l'unica soluzione? Anche allora non potevo garantire che chiunque usi la mia classe di visualizzazione non poteva fare qualcosa di sbagliato, come:

// If I implemented SetModel this way: 
void RenderView::SetModel(const std::shared_ptr<RenderModel>& model) 
{ 
    _model.reset(&*model); 
} 

RenderModel m; 
RenderView v; 
std::shared_ptr<RenderModel> ptr(&m); // create a shared_ptr from a stack-object. 
v.SetModel(ptr); 

risposta

8

come faccio impongono che un parametro non è stato allocato sullo stack?

Sì, richiedere al chiamante di fornire un std::shared_ptr<RenderModel>. Se il chiamante interpreta male lo std::shared_ptr, questo è il problema del chiamante, non il tuo.

Se si intende per un RenderView di essere l'unico proprietario di un particolare RenderModel, prendere in considerazione che la funzione di prendere un std::unique_ptr o std::auto_ptr invece; in questo modo è chiaro che il chiamante non dovrebbe mantenere la proprietà dell'oggetto dopo aver chiamato la funzione.

In alternativa, se RenderModel è a buon mercato per copiare, eseguire una copia di esso e utilizzare la copia:

_model.reset(new RenderModel(model)); 
+0

Se il chiamante non interpreta correttamente shared_ptr, presumo che non ci sia modo di rilevare qualcosa di simile? Chiedo semplicemente, perché sento che potrei fare di nuovo questo errore, e non voglio passare ore a ri-debugare il problema. Potrei lasciarmi una nota appiccicosa sul mio monitor fino a quando non mi brucia nella testa ... – Short

+0

Ci sono alcuni "trucchi" noti per rilevare se un oggetto vive nello stack o nell'heap, come confrontare gli indirizzi. Tutti questi relè si basano su un comportamento non definito o dipendente dall'implementazione. Suppongo che se è giusto accennare a te stesso potrebbe funzionare, ma non sono una soluzione reale. –

+3

No, se la tua funzione accetta 'std :: shared_ptr', non c'è modo di dire se quel puntatore punta a un oggetto valido. Detto questo, se prende esplicitamente un 'std :: shared_ptr', è molto più difficile sbagliare (il codice che costruisce un' std :: shared_ptr' da una variabile locale sembra a prima vista sbagliato ed è facile da evitare). –

3

probabilmente si dovrebbe definire la semantica della vostra classe in modo più chiaro. Se vuoi che RenderView sia il proprietario del RenderModel, dovrebbe crearlo da solo (magari inserire nel costruttore un identificatore da usare con una fabbrica).

Ho visto le classi che ricevono la proprietà degli oggetti ed è stato definito esplicitamente che questi oggetti devono essere nello heap, ma questo è, a mio parere, incline agli errori, proprio come l'errore che ora si è verificato. Non è possibile assegnare un oggetto stack a un puntatore intelligente che si aspetta che si trovi nell'heap (poiché verrà utilizzato per eliminarlo quando lo si desidera pulire).

+0

Qt usa il modello sopra, da dove ho avuto l'idea. Consente di collegare più viste a un modello. Dovrò esaminare la loro implementazione più da vicino, suppongo. – Short

2

Il modo in cui hai descritto cosa vuoi fare è completamente sbagliato. Nel modello di progettazione MVP, la vista non dovrebbe accedere direttamente al modello, ma dovrebbe inviare comandi al presentatore (richiamando le funzioni del presentatore).

In ogni caso, gli altri sono risposto alla tua domanda: l'oggetto del modello deve essere assegnato sul mucchio, in questo modo:

std::shared_ptr<RenderModel> ptr(new RenderModel); 
RenderView v; 
v.SetModel(ptr); 

altrimenti il ​​vostro oggetto shared_ptr sta per tentare di eliminare un oggetto stack.

1

Si dovrebbe tornare indietro e pensare al design. Il primo odore del codice è che stai prendendo un oggetto per riferimento e poi provi a gestirlo come un puntatore intelligente. Questo è errato.

Si dovrebbe iniziare decidendo chi è responsabile per la risorsa e progettare attorno ad esso, se RenderView è responsabile della gestione della risorsa, quindi non dovrebbe accettare un riferimento, ma piuttosto un puntatore (intelligente). Se è l'unico proprietario, la firma dovrebbe prendere un std::unique_ptr (o std::auto_ptr se il compilatore + libs non supporta unique_ptr), se la proprietà è diluita (preferibilmente fare un solo proprietario, ove possibile), quindi utilizzare uno shared_ptr.

Ma ci sono anche altri scenari in cui RenderView non ha bisogno di gestire la risorsa, nel qual caso può prendere il modello per riferimento e memorizzarlo per riferimento se non può essere modificato durante la vita di RenderView. In questo scenario, dove RenderView non è responsabile per la gestione della risorsa, non dovrebbe provare a delete mai (anche tramite un puntatore intelligente).

1
class ModelBase 
{ 
    //..... 
}; 

class RenderView 
{ 
    //.......... 
public: 
    template<typename ModelType> 
    shared_ptr<ModelType> CreateModel() { 
     ModelType* tmp=new ModelType(); 
     _model.reset(tmp); 
     return shared_ptr<ModelType>(_model,tmp); 
    } 

    shared_ptr<ModelBase> _model; 
    //...... 
}; 

Se il costruttore della classe del modello ha dei parametri, è possibile aggiungere parametri al metodo CreateModel() e utilizzare C++ 11 perfette tecniche di inoltro.

1

È necessario richiedere all'utente di passare correttamente l'input. Prima modifica il tuo tipo di input in un puntatore intelligente (piuttosto che in una variabile di riferimento). Secondo, passa loro correttamente il puntatore intelligente con un che non fa nulla (NoDelete sotto, esempio).

Un metodo di hacking sarebbe quello di controllare il segmento di memoria. Lo stack crescerà sempre dallo spazio del kernel (penso 0xC0000000 a 32 bit, qualcosa come 0x7fff2507e800 a 64 bit, basato sul codice seguente). Quindi puoi indovinare in base alla posizione della memoria se si tratta di una variabile stack o meno. La gente ti dirà che non è portabile, ma lo è, a meno che non si disponga di materiale distribuito su sistemi embedded.

#include <iostream> 
#include <memory> 

using namespace std; 

class foo 
{ 
    public: 
    foo(shared_ptr<int> in) { 
     cerr << in.get() << endl; 
     cerr << var.use_count() << endl; 
     var = in; 
     cerr << var.use_count() << endl; 
    }; 

    shared_ptr<int> var; 
}; 

struct NoDelete { 
    void operator()(int* p) { 
     std::cout << "Not Deleting\n"; 
    }; 
}; 

int main() 
{ 
    int myval = 5; 

    shared_ptr<int> valptr(&myval, NoDelete()); 
    foo staticinst(valptr); 

    shared_ptr<int> dynptr(new int); 
    *dynptr = 5; 
    foo dynamicinst(dynptr); 
} 
0

In breve: definire deleter personalizzato. In caso di un puntatore intelligente su un oggetto pila, è possibile costruire il puntatore intelligente con un deleter personalizzato, in questo caso un "Null Deleter" (o "StackObjectDeleter")

class StackObjectDeleter { 
public: 
    void operator() (void*) const {} 
}; 

std::shared_ptr<RenderModel> ptr(&m, StackObjectDeleter()); 

Lo StackObjectDeleter sostituisce il default_delete come l'oggetto deleter. default_delete chiama semplicemente delete (o delete []). In caso di StackObjectDeleter, non accadrà nulla.

+0

santo necro batman! – Short