2013-04-23 17 views
5

Podczas implementacji klasy fabrycznej napotkałem zachowanie std::auto_ptr, którego nie jestem w stanie zrozumieć. Zredukowałem problem do następującego małego programu, więc ... zacznijmy.Wzór Singleton: inne zachowanie auto_ptr i unique_ptr

Rozważmy następującą klasę singleton:

singleton.h

#ifndef SINGLETON_H_ 
#define SINGLETON_H_ 

#include<iostream> 
#include<memory> 

class singleton { 
public: 
    static singleton* get() { 
    std::cout << "singleton::get()" << std::endl; 
    if (!ptr_.get()) { 
     std::cout << &ptr_ << std::endl; 
     ptr_.reset(new singleton ); 
     std::cout << "CREATED" << std::endl; 
    } 
    return ptr_.get(); 
    } 

    ~singleton(){ 
    std::cout << "DELETED" << std::endl; 
    } 
private: 
    singleton() {} 
    singleton(const singleton&){} 

    static std::auto_ptr<singleton> ptr_; 
    //static std::unique_ptr<singleton> ptr_; 
}; 

#endif 

singleton.cpp

#include<singleton.h>o 
std::auto_ptr<singleton> singleton::ptr_(0); 
//std::unique_ptr<singleton> singleton::ptr_; 

Tutaj zastosowanie inteligentnego wskaźnika do zarządzania zasobami jest podyktowana głównie przez należy unikać wycieków przy wyjściu z programu. Używam wtedy ten kod w poniższym programie:

A.H

#ifndef A_H_ 
#define A_H_ 

int foo(); 

#endif 

a.cpp

#include<singleton.h> 

namespace { 
    singleton * dummy(singleton::get()); 
} 

int foo() { 
    singleton * pt = singleton::get(); 
    return 0; 
} 

main.cpp

#include<a.h> 

int main() { 

    int a = foo(); 

    return 0; 
} 

Teraz zabawny część. I skompilować trzy źródła oddzielnie:

$ g++ -I./ singleton.cpp -c 
$ g++ -I./ a.cpp -c 
$ g++ -I./ main.cpp -c 

Gdybym połączyć je wyraźnie w tej kolejności:

$ g++ main.o singleton.o a.o 

wszystko działa jak oczekuję, i pojawia się następujący stdout:

singleton::get() 
0x804a0d4 
CREATED 
singleton::get() 
DELETED 

Jeśli zamiast tego łączę źródła przy użyciu tego zamówienia:

$ g++ a.o main.o singleton.o 

uzyskać ten wynik:

singleton::get() 
0x804a0dc 
CREATED 
singleton::get() 
0x804a0dc 
CREATED 
DELETED 

Próbowałem różnych marek i kompilatora GNU (Intel) oraz wersje i to zachowanie jest zgodne między nimi. W każdym razie nie widzę kodu, którego zachowanie zależy od kolejności łączenia.

Ponadto, jeśli auto_ptr zastąpiono przez unique_ptr, zachowanie jest ZAWSZE zgodne z tym, czego się spodziewam.

To prowadzi mnie do pytania: Czy ktoś ma pojęcia, co się tutaj dzieje?

+0

jaka jest twoja wersja g ++? –

+2

Prawdopodobnie chcesz przeczytać [to pytanie] (http://stackoverflow.com/questions/86582/). – fredoverflow

Odpowiedz

4

Kolejność, w jakiej konstruowane są dummy i std::auto_ptr<singleton> singleton::ptr_(0), jest nieokreślona.

Dla przypadku auto_ptr, jeśli skonstruować dummy następnie singleton::ptr_(0), wartość tworzona w zaproszeniu dummy są usuwane przez konstruktora ptr_(0).

Dodałbym śledzenie do konstrukcji ptr_ przez ptr_(([](){ std::cout << "made ptr_\n"; }(),0)); lub coś w tym stylu.

Fakt, że współpracuje z unique_ptr jest przypadkowe, a być może ze względu na optymalizacje czym unique_ptr(0) można dowiedzieć się, że jest wyzerowany, jako taki nie robi nic (static dane są zerowane przed rozpoczęciem budowy, więc jeśli kompilator mógł dowiedzieć się, że unique_ptr(0) zeruje pamięć, może legalnie pominąć konstruktora, co oznacza, że ​​już nie zerujesz pamięci).

Jednym sposobem rozwiązania tego problemu jest użycie metody, która gwarantuje budowę przed użyciem, takie jak:

static std::auto_ptr<singleton>& get_ptr() { 
    static std::auto_ptr<singleton> ptr_(0); 
    return ptr_; 
    } 

i zastąpienie odniesień do ptr_ z get_ptr().

3

Kolejność budowy obiektów o zakresie plików zdefiniowanych w różnych jednostkach tłumaczeniowych jest nieokreślona. Jednak zazwyczaj obiekty zdefiniowane w jednostce tłumaczeniowej, która jest połączona przed inną jednostką tłumaczeniową, są konstruowane przed obiektami zdefiniowanymi w drugiej jednostce tłumaczeniowej. Różnica polega na tym, że są one powiązane z a.o i singleton.o. Kiedy singleton.o jest połączony przed a.o, singleton::ptr_ zostaje zainicjowany przed dummy i wszystko jest w porządku.Kiedy a.o jest połączone najpierw, najpierw zostaje zainicjowany dummy, który tworzy singleton; następnie singleton::ptr_ zostaje zainicjowane na 0, odrzucając wskaźnik do oryginalnej kopii singleton. Następnie w wywołaniu foo, wywołanie singleton::get() ponownie tworzy singleton.

+0

Wielkie dzięki za wyjaśnienie. Żałuję, że nie mogę przyjąć dwóch odpowiedzi ... – Massimiliano