Синглтон-паттерн: различное поведение auto_ptr и unique_ptr

При реализации фабричного класса я столкнулся с поведением std::auto_ptr что я не могу понять. Я сократил проблему до следующей маленькой программы, так что … начнем.

Рассмотрим следующий синглтон-класс:

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

Здесь использование умного указателя для управления ресурсом в основном продиктовано необходимостью избежать утечек при выходе из программы. Я использую этот код в следующей программе:

хиджры

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

Теперь смешной часть. Я собираю три источника отдельно:

$ 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 поведение ВСЕГДА соответствует тому, что я ожидаю, чтобы быть правильным.

Это подводит меня к вопросу: Кто-нибудь знает, что здесь происходит?

5

Решение

Порядок, в котором 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(),

4

Другие решения

Порядок построения объектов области файла, определенных в разных единицах перевода, не указан. Однако, как правило, объекты, определенные в модуле перевода, который связан до того, как другой модуль перевода создан, прежде чем объекты, определенные во втором модуле перевода. Разница здесь в том, в каком порядке a.o а также singleton.o связаны. когда singleton.o связан прежде a.o, singleton::ptr_ инициализируется раньше dummy и все хорошо. когда a.o связан первым, dummy сначала инициализируется, что создает синглтон; затем singleton::ptr_ инициализируется в 0, выбрасывая указатель на оригинальную копию singleton, Затем в вызове fooПризыв к singleton::get() строит синглтон снова.

3