Search code examples
c++staticlinkersingletonauto-ptr

Singleton pattern: different behavior of auto_ptr and unique_ptr


While implementing a factory class I encountered a behavior of std::auto_ptr that I am not able to understand. I reduced the problem down to the following small program, so ... let's start.

Consider the following singleton class:

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

Here the use of a smart pointer to manage the resource is mainly dictated by the need to avoid leaks at program exit. I use then this code in the following program:

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

Now the funny part. I compile the three sources separately:

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

If I link them explicitly in this order:

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

everything work as I expect, and I get the following to stdout:

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

If instead I link the sources using this order:

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

I get this output:

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

I tried different compiler brands (Intel and GNU) and versions and this behavior is consistent among them. Anyhow, I am not able to see code whose behavior depends on the order of linking.

Furthermore, if auto_ptr is substituted by unique_ptr the behavior is ALWAYS consistent with what I expect to be the correct one.

That brings me to the question: Does anybody have a clue on what's going on here?


Solution

  • The order at which dummy and std::auto_ptr< singleton > singleton::ptr_(0) is constructed is unspecified.

    For the auto_ptr case, if you construct dummy then singleton::ptr_(0), the value created in the dummy call is erased by the constructor of ptr_(0).

    I would add tracking to the construction of ptr_ via ptr_(([](){ std::cout << "made ptr_\n"; }(),0)); or something like that.

    The fact that it works with unique_ptr is coincidental, and possibly due to optimizations whereby unique_ptr(0) can figure out it is zeroed, as such does nothing (static data is zeroed before construction starts, so if the compiler could figure out that unique_ptr(0) just zeros the memory, it could legally skip the constructor, which means you no longer zero the memory).

    One way to fix this is to use a method that guarantees construction before use, such as:

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

    and replace references to ptr_ with get_ptr().