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