2011-09-27 21 views
8

Esto puede ser imposible, pero me preguntaba si era posible evitar que un temporal perdurara más allá de su expresión original. Tengo una cadena de objetos que apuntan a objetos padres, y una función miembro que creará un objeto secundario, un ejemplo simplificado es aquí¿Evita que los temporales prolonguen su vida útil?

class person{ 
    string name; 
    person * mommy; 
public: 
    person(const string & nam, person * m = 0) : name(nam), mommy(m) {} 
    person baby(const string & nam){ 
     return person(nam, this); 
    } 
    void talk() const{ 
     if (mommy) mommy->talk(); 
     cout << name << endl; 
    } 
}; 

int main(){ 
    person("Ann").baby("Susan").baby("Wendy").talk();  // fine 

    const person & babygirl = person("Julie").baby("Laura"); // not fine 

    babygirl.talk(); // segfault 
    return 0; 
} 

La forma en que quiero usar person es pasarlo a una función, y algo como esto:

void use(const person & p) { 
    p.talk(); 
} 
use(person("Anna").baby("Lisa")); 

Is fine.

Esto funcionará bien siempre que ninguno de los temporales sobreviva más allá de la expresión original, pero si vinculo uno de los temporales definitivos a una referencia constante, sus padres no sobreviven, y obtengo una segfault. Puedo ocultar el constructor de copias y el operador de asignaciones de person, pero ¿hay alguna manera de evitar este tipo de error? Me gustaría evitar la asignación dinámica si es posible.

+0

@Konrad: irónico; -] – ildjarn

+1

Tenga en cuenta que este código "no está bien" de la misma manera esa escritura 'const int & i = std :: vector (1) [0];' no está "bien". 'vector' no te impide escribir eso, y no necesita hacerlo. La clave aquí es que, debido a que la destrucción de la mamá hace que el bebé quede inutilizable, el bebé es parte de la mamá. Eso es lo que está mal con el diseño, es contrario a la intuición. Está tratando de arreglar eso evitando que exista algo así como un huérfano, lo que podría ser apropiado, pero también debería considerar si los huérfanos deberían tener un comportamiento mejor definido o si lo más obvio es que es algo malo para crear. –

+0

De acuerdo con Steve: esto es solo un mal diseño que muestra.La aparición de un puntero desnudo debería haber revelado que algo no funciona. –

Respuesta

3

Parece que está creando una estructura de datos aquí donde los niños tienen consejos para sus padres. El uso de los temporales te garantiza la pena en este caso. Para que esto sea seguro, deberá asignar dinámicamente y posiblemente utilizar algún tipo de recuento de referencias.

¿Ha considerado usar boost::shared_ptr? Es una implementación de una clase de puntero inteligente contada de referencia. Usando shared_ptr y tal vez algunos métodos de fábrica, es posible que pueda obtener el efecto que desea y reducir el dolor de la asignación de memoria dinámica. Lo probé y parece funcionar. Una vez que el código sale del alcance, los objetos se destruyen porque no quedan referencias para los shared_ptrs.

Editar: En respuesta a zounds' comentario, he modificado el ejemplo de modo que el objeto raíz controla la vida de la estructura de datos.

#include <iostream> 
#include <string> 
#include <vector> 
#include <boost\shared_ptr.hpp> 
#include <boost\weak_ptr.hpp> 

using boost::shared_ptr; 
using boost::weak_ptr; 

using std::string; 
using std::cout; 
using std::endl; 
using std::vector; 

class person; 
typedef shared_ptr<person> Person; 
typedef weak_ptr<person> PersonWk; 

class person {  
    PersonWk pThis; 
    friend Person makePerson(const string & nam, Person m = Person()); 

    string name; 
    PersonWk mommy; // children should not affect parent lifetime, so store weak ptr 
    vector<Person> children; // parents affect children lifetime so store child shared ptrs 

    // make constructor private so that you can only make a person using factory method 
    person(const string & nam, Person m) : name(nam), mommy(m) 
    { 
     // for demo purposes 
     printf("creating %s\n", nam.c_str()); 
     ++personCount; 
    } 

    // undefined copy constructor and assignment operators 
    person(const person&); 
    person& operator=(const person&); 

public: 
    // for demo purposes 
    static int personCount; 

    ~person() 
    { 
     // for demo purposes 
     printf("destroying %s\n", name.c_str()); 
     --personCount; 
    } 

    Person baby(const string & nam){   
     Person child = makePerson(nam, Person(pThis)); 
     children.push_back(child); 
     return child; 
    } 

    void talk() const{ 
     if (Person mom = mommy.lock()) 
      mom->talk(); 
     cout << name << endl; 
    } 
}; 

int person::personCount = 0; 

// factory method to make a person 
Person makePerson(const string & name, Person m) { 
    Person p = Person(new person(name, m)); 
    p->pThis = p; // stash weak_ptr so I can use it to make a shared_ptr from "this" in the baby method 
    return p; 
} 

void use(const Person p) { 
    printf("In method use...\n"); 
    p->talk(); 
} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    printf("personCount=%d\n", person::personCount); 
    { 
     Person ann = makePerson("Ann"); 

     // ann has baby and grandbaby, pass grandbaby to use method 
     use(ann->baby("Susan")->baby("Wendy")); 

     ann.reset(); // remove reference from root object. Destruction ensues... 
    } 
    printf("personCount=%d\n", person::personCount); 
    return 0; 
} 
+0

Gracias por la respuesta. El problema para mí con ptr compartida es que el objeto raíz puede necesitar tener un punto de destrucción específico, y no quiero preocuparme de que pueda haber un puntero almacenado en algún lugar. Solo necesito que los temporales existan durante tanto tiempo como una llamada a función, y luego pueden morir. Estoy usando una estructura padre-hijo donde los niños no pueden afectar a sus padres (excepto a través de la interfaz pública normal que todos pueden usar) – zounds

+0

Un enfoque podría ser almacenar el objeto raíz en una ubicación conocida. Luego, haga que cada padre mantenga shared_ptrs a sus hijos y los hijos puedan mantener weak_ptrs a sus padres. Luego, cuando establece el objeto raíz en NULL (eliminando así todas las referencias), el árbol se destruye de arriba a abajo. –

+0

Ejemplo de código modificado para utilizar el enfoque en mi comentario anterior. –

0

Vas a tener que hacer algo como esto:

void use(const person & p) { 
    p.talk(); 
} 
person a("Anna"); 
use(a.baby("Lisa")); 

De esta manera, el padre "a" no sale del alcance hasta que está realmente terminado con él (después de la llamar a "usar").

El problema con el código original es que el padre "Anna" solo tiene que permanecer el tiempo suficiente para llamar "bebé" y el padre puede descartarse antes de realizar la llamada a la función. Al hacer que el padre sea una variable con alcance, puede controlar cuándo se destruye.

¿Esto me parece peligroso? Sí. Por lo tanto, sugeriría una mirada a la respuesta de m-sharp sobre la asignación dinámica. Pero si quiere un método que no necesita recuento de referencias, etc., puede hacerlo de esta manera ...

Cuestiones relacionadas