Singleton pattern: different behavior of auto_ptr and unique_ptr
Asked Answered
F

2

5

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?

Flaviaflavian answered 23/4, 2013 at 19:42 Comment(2)
what is your g++ version?Patty
You probably want to read this question.Abort
O
4

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().

Opsonize answered 23/4, 2013 at 19:56 Comment(0)
C
3

The order of construction of file-scope objects defined in different translation units is unspecified. However, typically, objects defined in a translation unit that is linked before another translation unit are constructed before objects defined in the second translation unit. The difference here is the order in which a.o and singleton.o are linked. When singleton.o is linked before a.o, singleton::ptr_ gets initialized before dummy and all is well. When a.o is linked first, dummy gets initialized first, which constructs the singleton; then singleton::ptr_ gets initialized to 0, throwing away the pointer to the original copy of singleton. Then in the call to foo, the call to singleton::get() constructs the singleton again.

Contraception answered 23/4, 2013 at 19:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.