2011-09-27 32 views
8

Questo potrebbe essere impossibile, ma mi chiedevo se fosse possibile mantenere una temporanea da sempre duratura rispetto alla sua espressione originale. Ho una catena di oggetti che puntano a oggetti padre, e una funzione di membro che creerà un oggetto figlio, un esempio semplificato è quiImpedire temporaneamente di estendere la sua durata?

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

Il modo in cui voglio usare person è quello di passare a una funzione, e qualcosa del genere:

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

Va bene.

Questo funzionerà bene fino a quando nessuno dei provvisori sopravviverà oltre l'espressione originale, ma se lego uno dei provvisori finali a un riferimento const, i suoi genitori non sopravvivono e io ottengo un segfault. Posso nascondere il costruttore di copie e l'assegnatore di copie di person, ma esiste un modo per evitare che si verifichi questo tipo di errore? Vorrei evitare l'allocazione dinamica se possibile.

+0

@Konrad: Ironico; -] – ildjarn

+1

Nota che questo codice è "non buono" allo stesso modo quella scrittura 'const int & i = std :: vector (1) [0];' is "non fine". 'vector' non ti impedisce di scriverlo e non ne ha bisogno. La chiave qui è che, poiché distruggere la mamma rende il bambino inutilizzabile, il bambino fa parte della mamma. Questo è ciò che è sbagliato nel design, è contro-intuitivo. Stai provando ad aggiustare tutto ciò impedendo che ci sia qualcosa di orfano, il che potrebbe essere appropriato, ma dovresti anche considerare se gli orfani dovrebbero semplicemente avere un comportamento meglio definito, o dovrebbe essere ovviamente una cosa cattiva da creare. –

+0

Concordato con Steve: questo è solo un cattivo design che mostra.L'aspetto di un puntatore nudo dovrebbe aver dato via che qualcosa non va. –

risposta

3

Sembra che tu stia creando una struttura dati in cui i bambini hanno indicazioni per i loro genitori. In questo caso è garantito che l'uso dei provvisori causi dolore. Al fine di rendere questo sicuro, è necessario allocare dinamicamente e possibilmente utilizzare una sorta di conteggio dei riferimenti.

Avete considerato l'utilizzo di boost::shared_ptr? È un'implementazione di una classe di puntatore intelligente conteggiata di riferimento. Usando shared_ptr e forse alcuni metodi di fabbrica, potresti essere in grado di ottenere l'effetto desiderato e ridurre il dolore dell'allocazione dinamica della memoria. L'ho provato e sembra funzionare. Una volta che il codice esce dall'ambito, gli oggetti vengono tutti distrutti perché non ci sono riferimenti a shared_ptrs.

Edit: In risposta a zounds' commento, ho modificato l'esempio in modo che l'oggetto principale controlla la vita della struttura dati.

#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

Grazie per la risposta. Il problema per me con ptr condiviso è che l'oggetto root potrebbe aver bisogno di avere uno specifico punto di distruzione, e non voglio preoccuparmi che possa esserci un puntatore memorizzato da qualche parte. Ho solo bisogno che i temporari esistano per tutta la durata di una chiamata di funzione, e quindi possono morire. Sto usando una struttura padre-> genitore in cui i bambini non possono influenzare i loro genitori (tranne attraverso la normale interfaccia pubblica che tutti possono utilizzare) – zounds

+0

Un approccio potrebbe essere quello di memorizzare l'oggetto radice in una posizione nota. Poi fai in modo che ogni genitore mantenga shared_ptrs ai loro figli e i bambini possano mantenere weak_ptrs ai loro genitori. Quindi, quando si imposta l'oggetto radice su NULL (rimuovendo quindi tutti i riferimenti), l'albero viene distrutto dall'alto verso il basso. –

+0

Esempio di codice modificato per utilizzare l'approccio nel mio commento precedente. –

0

Dovrete fare qualcosa di simile:

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

In questo modo, il genitore "a" non si spegne del campo di applicazione fino a quando hai veramente finito con esso (dopo il chiama a "usare").

Il problema con il codice originale è che il genitore "Anna" deve rimanere in giro abbastanza a lungo da chiamare "baby" e il genitore può essere scartato prima di effettuare la chiamata di funzione. Rendendo la variabile genitore con scope, puoi controllare quando viene distrutta.

Questo sembra pericoloso per me? Sì. Pertanto suggerirei di dare un'occhiata alla risposta di m-sharp sull'allocazione dinamica. Ma se vuoi un metodo che non ha bisogno del conteggio dei riferimenti, ecc. Allora puoi farlo in questo modo ...

Problemi correlati