4

I implemented a simple singleton by using boost::scoped_ptr:

template <class T> class Singleton : public boost::noncopyable {
   public:
       static T& instance() {
           boost::call_once(init, flag);
           return *t;
       }
       static void init() {
           t.reset(new T());
       }
    private:
       static boost::scoped_ptr <T> t;
      static boost::once_flag flag;
 };
 template <class T> boost::scoped_ptr<T> Singleton<T>::t(0);
 template <class T> boost::once_flag Singleton<T>::flag = BOOST_ONCE_INIT;

Define a real singleton class:

 class S : public Singleton<S> {
     public:
        void p() { printf("hello");}
 };

Then I define a static variable in file S.cpp:

static volatile S &S_instance = S::instance();

in main.cpp:

 int main()
 {
      S &ss = S::instance();
      ss.p();
 }

Run this program, an exception happened:

/usr/include/boost/smart_ptr/scoped_ptr.hpp:91: T& boost::scoped_ptr::operator*() const [with T = S]: Assertion `px != 0' failed

Tracing the code, I found the static s_instance.t is cleared once the code leaves the static initialization segment and after that all code referring to the S::instance will get NULL scoped_ptr. Does anybody know the reason?

[UPDATE] I tried to put all static into one cpp file (S1.cpp):

template <class T> boost::scoped_ptr<T> Singleton<T>::t(0);
template <class T> boost::once_flag Singleton<T>::flag = BOOST_ONCE_INIT;

static S& s_ins = S::instance();

and debug it with GDB, it looks follow the order I wrote. any idea?

eric zhu
  • 51
  • 6

4 Answers4

3

A possible reason is that the static template <class T> boost::scoped_ptr<T> Singleton<T>::t(0); is initialized after the static volatile S &S_instance = S::instance();, and thus it replaced with 0 the value previously stored in t. The order in which the static variables are constructed is only defined within a single compilation unit, and I guess in your case t can be instantiated inside main.cpp (or rather in both files at compilation time, and the linker will have to choose just one) while S resides in S.cpp. Just a guess though.

Alexey Kukanov
  • 12,479
  • 2
  • 36
  • 55
0

Your program works correctly (when compiled as a single file) if you remove this line:

static volatile S &S_instance = S::instance();

OK. When built on my machine without your S_instance declaration:

0000000000400d86 <__static_initialization_and_destruction_0(int, int)>:
  400d86:       55                      push   %rbp
  400d87:       48 89 e5                mov    %rsp,%rbp
  400d8a:       48 83 ec 10             sub    $0x10,%rsp
  400d8e:       89 7d fc                mov    %edi,-0x4(%rbp)
  400d91:       89 75 f8                mov    %esi,-0x8(%rbp)
  400d94:       83 7d fc 01             cmpl   $0x1,-0x4(%rbp)
  400d98:       75 43                   jne    400ddd <__static_initialization_and_destruction_0(int, int)+0x57>
  400d9a:       81 7d f8 ff ff 00 00    cmpl   $0xffff,-0x8(%rbp)
  400da1:       75 3a                   jne    400ddd <__static_initialization_and_destruction_0(int, int)+0x57>
  400da3:       b8 b8 40 40 00          mov    $0x4040b8,%eax
  400da8:       0f b6 00                movzbl (%rax),%eax
  400dab:       84 c0                   test   %al,%al
  400dad:       75 2e                   jne    400ddd <__static_initialization_and_destruction_0(int, int)+0x57>
  400daf:       b8 b8 40 40 00          mov    $0x4040b8,%eax
  400db4:       c6 00 01                movb   $0x1,(%rax)
  400db7:       be 00 00 00 00          mov    $0x0,%esi
  400dbc:       bf b0 40 40 00          mov    $0x4040b0,%edi
  400dc1:       e8 3c 05 00 00          callq  401302 <boost::scoped_ptr<S>::scoped_ptr(S*)>
  400dc6:       b8 da 13 40 00          mov    $0x4013da,%eax
  400dcb:       ba 90 40 40 00          mov    $0x404090,%edx
  400dd0:       be b0 40 40 00          mov    $0x4040b0,%esi
  400dd5:       48 89 c7                mov    %rax,%rdi
  400dd8:       e8 8b fd ff ff          callq  400b68 <__cxa_atexit@plt>
  400ddd:       c9                      leaveq 
  400dde:       c3                      retq  

When compiled with your S_instance declaration:

0000000000400d86 <__static_initialization_and_destruction_0(int, int)>:
  400d86:       55                      push   %rbp
  400d87:       48 89 e5                mov    %rsp,%rbp
  400d8a:       48 83 ec 10             sub    $0x10,%rsp
  400d8e:       89 7d fc                mov    %edi,-0x4(%rbp)
  400d91:       89 75 f8                mov    %esi,-0x8(%rbp)
  400d94:       83 7d fc 01             cmpl   $0x1,-0x4(%rbp)
  400d98:       75 4f                   jne    400de9 <__static_initialization_and_destruction_0(int, int)+0x63>
  400d9a:       81 7d f8 ff ff 00 00    cmpl   $0xffff,-0x8(%rbp)
  400da1:       75 46                   jne    400de9 <__static_initialization_and_destruction_0(int, int)+0x63>
  400da3:       e8 c2 04 00 00          callq  40126a <Singleton<S>::instance()>
  400da8:       48 89 05 01 33 00 00    mov    %rax,0x3301(%rip)        # 4040b0 <S_instance>
  400daf:       b8 c0 40 40 00          mov    $0x4040c0,%eax
  400db4:       0f b6 00                movzbl (%rax),%eax
  400db7:       84 c0                   test   %al,%al
  400db9:       75 2e                   jne    400de9 <__static_initialization_and_destruction_0(int, int)+0x63>
  400dbb:       b8 c0 40 40 00          mov    $0x4040c0,%eax
  400dc0:       c6 00 01                movb   $0x1,(%rax)
  400dc3:       be 00 00 00 00          mov    $0x0,%esi
  400dc8:       bf b8 40 40 00          mov    $0x4040b8,%edi
  400dcd:       e8 3c 05 00 00          callq  40130e <boost::scoped_ptr<S>::scoped_ptr(S*)>
  400dd2:       b8 e6 13 40 00          mov    $0x4013e6,%eax
  400dd7:       ba 90 40 40 00          mov    $0x404090,%edx
  400ddc:       be b8 40 40 00          mov    $0x4040b8,%esi
  400de1:       48 89 c7                mov    %rax,%rdi
  400de4:       e8 7f fd ff ff          callq  400b68 <__cxa_atexit@plt>
  400de9:       c9                      leaveq 
  400dea:       c3                      retq   

In the latter code, you can clearly see that the constructor for the static scoped_ptr happens after S_instance.

The above were compiled from:

#include <cstdio>

#include <boost/scoped_ptr.hpp>
#include <boost/thread/once.hpp>
#include <boost/noncopyable.hpp>

template <class T> class Singleton : public boost::noncopyable {
public:
    static T& instance() {
        boost::call_once(init, flag);
        return *t;
    }
    static void init() {
        t.reset(new T());
    }
private:
    static boost::scoped_ptr <T> t;
    static boost::once_flag flag;
};

template <class T> boost::scoped_ptr<T> Singleton<T>::t(0);
template <class T> boost::once_flag Singleton<T>::flag = BOOST_ONCE_INIT;

class S : public Singleton<S> {
public:
    void p() { printf("hello");}
};

// static volatile S &S_instance = S::instance();

int main()
{
    S &ss = S::instance();
    ss.p();
}
acm
  • 12,183
  • 5
  • 39
  • 68
0

I'm pretty sure that it is undefined behavior due to undefined creation order of global variables. So, your S_instance first initialized, and then template <class T> boost::scoped_ptr<T> Singleton<T>::t(0)

Such a simple program can illustrate, what can happen when order is reversed:

#include <iostream>

std::string &getS();
std::string& t = getS();
std::string s("hello");
std::string &getS() {s = "world"; return s;}

int main()
{
    std::cout << t;
}

Well, it crashes for me with g++ and prints hello with cl

Lol4t0
  • 12,444
  • 4
  • 29
  • 65
0

I think that this is wrong:

static volatile S &S_instance = S::instance();

because it creates and then deletes the instance. You want a pointer, not a reference.

static S *S_instance = &S::instance();

As far as I know the reference goes out of scope at the end of the .cpp file. The instance is not expected to ever be deleted anyway, so T can just be a bare pointer.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156