Why boost :: scoped_ptr is cleared in a singleton implementation

I implemented a simple singleton 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 the S.cpp file:

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

in main.cpp:

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

Run this program, an exception occurred:

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

Tracking the code, I found that static s_instance.t is cleared after the code leaves the static initialization segment, and after that all the code that references the S :: instance will receive NULL scoped_ptr. Does anyone know the reason?

[UPDATE] I tried putting all the static in a single 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 using GDB, it looks like the following I wrote. any idea?

+4
source share
4 answers

The possible reason is that the static template <class T> boost::scoped_ptr<T> Singleton<T>::t(0); initialized after static volatile S &S_instance = S::instance(); , and therefore it is replaced with 0 by the value previously stored in t . The order in which the static variables are built is determined in only one compilation unit, and I assume that in your case t can be created inside main.cpp (or, rather, in both files at compile time, and the linker will only have to select one ), and S is in S.cpp . Just suppose though.

+2
source

Your program works correctly (if it is compiled as a single file) if you delete this line:

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

OK When building 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 compiling 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 last code, you can clearly see that the constructor for static scoped_ptr occurs after S_instance.

The following were compiled:

 #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(); } 
0
source

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

Such a simple program can illustrate what can happen when the order is canceled:

 #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 using cl

0
source

I think it's wrong:

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

as it creates and deletes the instance. You need a pointer, not a link.

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

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

0
source

Source: https://habr.com/ru/post/1390872/


All Articles