2011-09-12 23 views
12

He estado aprendiendo punteros administrados últimamente y encontré el siguiente escenario.C++ shared_ptr of stack object

Estoy implementando una clase de modelo/controlador para una vista de juego. Mi punto de vista, representará las cosas en el modelo. Muy claro. En mi función principal, crear instancias de los tres de esta manera:

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

Mi render clase de vista es bastante sencillo:

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; 
}; 

La aplicación de setView es bastante estándar:

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

El La clave para esto es que la vista almacena un modelo en un puntero inteligente. Sin embargo, en principio, el modelo se asignó en la pila. Cuando el programa sale, la memoria se elimina dos veces. Esto tiene sentido. Mi comprensión actual me dice que todo lo que se almacena en un smart_ptr (de cualquier tipo) no debería haber sido asignado en la pila.

Después de toda la configuración anterior, mi pregunta es simple: ¿cómo puedo dictar que un parámetro no se haya asignado en la pila? ¿Aceptar un puntero inteligente como parámetro es la única solución? Incluso entonces no podía garantizar que alguien que use mi clase de vista no podía hacer algo incorrecto, tales como:

// 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); 

Respuesta

8

¿cómo lo dictan que un parámetro no fue asignado en la pila?

Sí, requiere que la persona que llama proporcione un std::shared_ptr<RenderModel>. Si la persona que realiza la llamada crea mal el std::shared_ptr, ese es el problema del que llama, no el tuyo.

Si tiene la intención de RenderView a ser el único propietario de un particular, RenderModel, considere que tiene la función toma un std::unique_ptr o std::auto_ptr lugar; de esta manera, está claro que la persona que llama no debe retener la propiedad del objeto después de llamar a la función.

Alternativamente, si RenderModel es barato para copiar, haga una copia del mismo y utilizar la copia:

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

Si la persona que llama crea mal la shared_ptr, supongo que no hay forma de detectar algo como esto? Pregunto simplemente, porque creo que puedo cometer este error otra vez, y no quiero pasar horas re-depurando el problema. Podría dejarme una nota adhesiva en mi monitor hasta que se incinere en mi cabeza ... – Short

+0

Existen algunos 'trucos' conocidos para detectar si un objeto vive en la pila o en el montón, como la comparación de direcciones. Todos estos transmiten el comportamiento indefinido o dependiente de la implementación. Supongo que si es solo para insinuarse, puede funcionar, pero no son una solución real. –

+3

No, si su función toma 'std :: shared_ptr', no hay forma de saber si ese puntero apunta a un objeto válido. Dicho esto, si toma explícitamente un 'std :: shared_ptr', es mucho más difícil equivocarse (el código que construye un' std :: shared_ptr' a partir de una variable local se ve mal a primera vista y es fácil de evitar). –

3

probablemente debería definir la semántica de su clase con mayor claridad. Si desea que RenderView sea el propietario de RenderModel, debe crearlo solo (tal vez obtener en el constructor algún identificador para usar con una fábrica).

He visto clases que reciben propiedad de objetos, y fue definir explícitamente que estos objetos deben estar en el montón, pero esto es, en mi opinión, propenso a errores, al igual que el error que se encuentra. No puede asignar un objeto de pila a un puntero inteligente que espera que esté en el montón (porque usará eliminarlo cuando quiera limpiarlo).

+0

Qt usa el modelo anterior, de ahí es de donde surgió la idea. Permite vincular varias vistas a un modelo. Tendré que examinar su implementación más de cerca, supongo. – Short

2

La forma en que describió lo que quiere hacer es completamente incorrecta. En el patrón de diseño MVP, la vista no debe acceder al modelo directamente, sino que debe enviar comandos al presentador (llamando a las funciones del presentador).

De todos modos, otros han de respondido a su pregunta: el objeto del modelo tiene que ser asignada en el montón, así:

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

de lo contrario su objeto shared_ptr va a tratar de eliminar un objeto de pila.

1

Debe volver y pensar en el diseño. El primer olor a código es que está tomando un objeto por referencia y luego tratando de administrarlo como un puntero inteligente. Eso es mal.

Debe comenzar por decidir quién es el responsable del recurso y diseñar a su alrededor, si RenderView es responsable de la gestión del recurso, entonces no debe aceptar una referencia, sino un puntero (inteligente). Si es el único propietario, la firma debe tomar un std::unique_ptr (o std::auto_ptr si su compilador + libs no admite unique_ptr), si la propiedad está diluida (prefiere hacer un único propietario siempre que sea posible), luego use un shared_ptr.

Pero también hay otros escenarios donde RenderView no necesita administrar el recurso en absoluto, en cuyo caso puede tomar el modelo por referencia y almacenarlo por referencia si no se puede cambiar durante la vida útil de RenderView. En este escenario, donde RenderView no es responsable de la administración del recurso, no debería intentar delete (incluso a través de un puntero inteligente).

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; 
    //...... 
}; 

Si el constructor de la clase modelo tiene parámetros, es posible añadir parámetros a CreateModel método() y utilizar C++ 11 técnicas de reenvío perfecto.

1

Debe solicitar al usuario que pase correctamente la entrada. Primero cambie su tipo de entrada a un puntero inteligente (en lugar de una variable de referencia). Segundo, pídales que pasen correctamente el puntero inteligente con un que no haga nada (NoDelete a continuación, ejemplo).

Un método malicioso consistiría en verificar el segmento de memoria. La pila siempre va a crecer desde el espacio del kernel (creo que 0xC0000000 en 32 bits, algo así como 0x7fff2507e800 en 64 bits, según el código siguiente). Entonces, podría adivinar en función de la ubicación de la memoria, ya sea que sea una variable de pila o no. La gente te dirá que no es portátil, pero lo es, a menos que vayas a implementar cosas en sistemas integrados.

#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

En resumen: defina el autorradio personalizado. En caso de un puntero inteligente en un objeto de pila, se puede construir el puntero inteligente con un Deleter costumbre, en este caso un "nulo Deleter" (o "StackObjectDeleter")

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

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

El StackObjectDeleter sustituye a la default_delete como el objeto eliminador. default_delete simplemente llama a delete (o delete []). En el caso de StackObjectDeleter, no pasará nada.

+0

holy necro batman! – Short