2011-09-27 18 views
8

Może to być niemożliwe, ale zastanawiałem się, czy możliwe było zachowanie tymczasowego odlegle od jego pierwotnego wyrażenia. Mam sieć obiektów, które wskazują na obiektach nadrzędnych, a funkcja członek, który stworzy obiekt podrzędny, uproszczonym przykładem jest tutajZapobieganie czasowemu przedłueniu jego okresu trwałości?

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

Sposób chcę użyć person jest przekazanie go do funkcji, i coś takiego:

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

Jest w porządku.

To zadziała dobrze, o ile żaden z tymczasników nie przeżyje poza pierwotnym wyrażeniem, ale jeśli zwiążę jeden z ostatnich tymczasowych do odniesienia do const, jego rodzice nie przetrwają, a ja dostaję segfault. Mogę ukryć konstruktora kopii i operatora przypisania, ale czy jest jakiś sposób, aby zapobiec temu rodzajowi błędu? Jeśli to możliwe, chciałbym uniknąć alokacji dynamicznej.

+0

@Konrad: Ironiczny; -] – ildjarn

+1

Zauważ, że ten kod jest "nie w porządku" w taki sam sposób że pisanie 'const int & i = std :: vector (1) [0];' jest "nie w porządku". 'vector' nie przestajesz pisać tego i nie musisz tego robić. Kluczem jest to, że ponieważ niszczenie mamusi powoduje, że dziecko nie nadaje się do użytku, dziecko jest częścią mamusi. To, co jest nie tak z projektem, jest sprzeczne z intuicją. Próbujesz to załatać, uniemożliwiając istnienie czegoś takiego jak sierota, co może być odpowiednie, ale powinieneś również rozważyć, czy sieroty powinny mieć lepiej zdefiniowane zachowanie, czy też powinny być w oczywisty sposób złe. –

+0

Uzgodniono ze Steve'em: to tylko zły projekt.Pojawienie się gołego wskaźnika powinno spowodować, że coś jest nie tak. –

Odpowiedz

3

Wygląda na to, że tworzysz strukturę danych, w której dzieci mają wskaźniki do rodziców. Korzystanie z tymczasowych gwarantuje w tym przypadku żal. Aby było to bezpieczne, będziesz musiał dynamicznie przydzielać i ewentualnie używać pewnego rodzaju liczenia odwołań.

Czy rozważałeś użycie boost::shared_ptr? Jest to implementacja referencyjnej zliczonej klasy inteligentnego wskaźnika. Korzystanie z shared_ptr i być może niektórych metod fabrycznych, może być w stanie uzyskać pożądany efekt i zmniejszyć ból dynamicznej alokacji pamięci. Wypróbowałem to i wygląda na to, że działa. Gdy kod wychodzi z zakresu, wszystkie obiekty są niszczone, ponieważ nie ma żadnych odniesień do shared_ptrs.

Edit: W odpowiedzi na komentarz Zounds', mam zmodyfikowany przykład tak, że obiekt główny kontroluje żywotność struktury te dane.

#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

Dzięki za odpowiedź. Problem dla mnie ze współdzielonym ptr polega na tym, że obiekt główny może wymagać określonego punktu destrukcji i nie chcę się martwić, że może tam gdzieś przechowywać zapisany wskaźnik. Potrzebuję tylko tymczasowych istnieć tak długo, jak wywołanie funkcji, a następnie mogą one umrzeć. Używam struktury potomnej-> macierzystej, w której dzieciom nie wolno wpływać na rodziców (z wyjątkiem normalnego publicznego interfejsu, z którego każdy może korzystać). – zounds

+0

Jednym z podejść może być przechowywanie obiektu głównego w znanej lokalizacji. Następnie niech każdy z rodziców zachowuje shared_ptrs dla swoich dzieci, a dzieci mogą przechowywać weak_ptrs dla swoich rodziców. Następnie, po ustawieniu obiektu głównego na wartość NULL (usuwając wszystkie odwołania), drzewo jest niszczone od góry do dołu. –

+0

Zmodyfikowano próbkę kodu, aby zastosować podejście z mojego poprzedniego komentarza. –

0

Musisz zrobić coś takiego:

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

ten sposób rodzic „a” nie wykracza poza zakres dopóki nie jesteś naprawdę z nim zrobić (po zadzwoń do "użytkowania").

Problem z oryginalnym kodem polega na tym, że rodzic "Anna" musi zostać na tyle długo, aby zadzwonić "dziecko", a rodzic może zostać odrzucony przed wykonaniem wywołania funkcji. Poprzez ustawienie zmiennej nadrzędnej na zakres można kontrolować jej zniszczenie.

Czy to wygląda dla mnie niebezpiecznie? Tak. Dlatego sugeruję spojrzenie na odpowiedź m-sharp na alokację dynamiczną. Ale jeśli chcesz zastosować metodę, która nie wymaga liczenia odwołań itp., Możesz to zrobić w ten sposób ...