Синглтон картина: различное поведение авто 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 5

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() снова создает синглет.