Синглтон картина: различное поведение авто PTR и уникальный ПТР
При реализации фабричного класса я столкнулся с поведением std::auto_ptr
, которое я не в состоянии понять. Я свел проблему к следующей небольшой программе, так что ... давайте начнем.
Рассмотрим следующий синглетный класс:
Синглтон.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
Синглтон.cpp
#include<singleton.h>o
std::auto_ptr< singleton > singleton::ptr_(0);
//std::unique_ptr< singleton > singleton::ptr_;
Здесь использование интеллектуального указателя для управления ресурсом в основном продиктовано необходимостью избежать утечек при выходе из программы. Затем я использую этот код в следующем программа:
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;
}
Главная.cpp
#include<a.h>
int main() {
int a = foo();
return 0;
}
Теперьсмешная часть. Я компилирую три источника отдельно:
$ g++ -I./ singleton.cpp -c
$ g++ -I./ a.cpp -c
$ g++ -I./ main.cpp -c
Если я свяжу их явно в таком порядке:
$ g++ main.o singleton.o a.o
Все работает так, как я ожидаю, и я получаю следующее в stdout:
singleton::get()
0x804a0d4
CREATED
singleton::get()
DELETED
Если вместо этого я свяжу источники, используя этот порядок:
$ g++ a.o main.o singleton.o
Я получаю такой результат:
singleton::get()
0x804a0dc
CREATED
singleton::get()
0x804a0dc
CREATED
DELETED
Я пробовал разные компиляторы (Intel и GNU) и версии и такое поведение согласуется между собой. Во всяком случае, я не могу видеть код, поведение которого зависит от порядка связывания.
Кроме того, еслиauto_ptr
заменяется unique_ptr
, то поведение всегда согласуется с тем, что я ожидаю быть правильным.
Это подводит меня к вопросу: есть ли у кого-нибудь ключ к пониманию того, что здесь происходит?2 ответа:
Порядок, в котором строятся
dummy
иstd::auto_ptr< singleton > singleton::ptr_(0)
, не определен.Для случая
auto_ptr
, Если вы строитеdummy
, тоsingleton::ptr_(0)
, значение, созданное в вызовеdummy
, стирается конструкторомptr_(0)
.Я бы добавил отслеживание к построению
ptr_
черезptr_(([](){ std::cout << "made ptr_\n"; }(),0));
или что-то в этом роде.Тот факт, что он работает с
Один из способов исправить это-использовать метод, который гарантирует построение перед использованием, например:unique_ptr
, является случайным, и, возможно, из-за оптимизации, в результате которойunique_ptr(0)
может вычислить, что он обнулен, так как это ничего не делает (static
обнуляется перед началом построения, так что если компилятор сможет вычислить, чтоunique_ptr(0)
просто обнуляет память, он может законно пропустить конструктор, что означает, что вы больше не обнуляете память).static std::auto_ptr< singleton >& get_ptr() { static std::auto_ptr< singleton > ptr_(0); return ptr_; }
И заменить ссылки на
ptr_
наget_ptr()
.
Порядок построения объектов области видимости файла, определенных в различных единицах перевода, не определен. Однако, как правило, объекты, определенные в единице перевода, которая связана до другой единицы перевода, создаются до объектов, определенных во второй единице перевода. Разница здесь заключается в порядке, в котором
a.o
иsingleton.o
связаны. Когдаsingleton.o
связано раньшеa.o
,singleton::ptr_
инициализируется передdummy
, и все хорошо. Когдаa.o
связывается первым,dummy
инициализируется во-первых, который создает синглет; затемsingleton::ptr_
инициализируется до 0, отбрасывая указатель на исходную копиюsingleton
. Затем в вызовеfoo
вызовsingleton::get()
снова создает синглет.