Confusing difference in raw versus shared_ptr behavior w/ static initializers

R

Rudi Cilibrasi

Hi everybody,

I am struggling to understand the different behavior exhibited in the
raw versus shared_ptr behavior of the static initializers in the
following small test program. Can anybody explain this to me, or is
it perhaps a compiler bug?
I am trying to get good at RAII and cannot figure this puzzle out.

Any helpful info will be much appreciated. Code pasted below. Best
regards,

Rudi

// Compiled with g++ 4.5.2 using command:
//
// g++ main.cc -o noshared -std=c++0x
// or
// g++ main.cc -o yesshared -std=c++0x -DUSED_SHARED=1
//
// How come the first case produces two lines of output and the second
only 1?
//
// % ./noshared
// cmdstring
// hello
// %
// % ./yesshared
// hello
// %



#include <string>
#include <vector>
#include <iostream>

#include <memory>


class Anac {
std::vector<std::string> m_strings;
Anac(void);
public:
static Anac& getInstance(void);
void printStrings(void);
void addString(std::string val);
};

class Cmd {
public:
Cmd(void);
};

static Cmd c;

Cmd::Cmd(void)
{
Anac::getInstance().addString("cmdstring");
}

using namespace std;

#if USED_SHARED
static std::shared_ptr<Anac> singleton;
#else
static Anac *singleton;
#endif

Anac::Anac(void) : m_strings() {
}

void Anac::addString(std::string val) {
m_strings.push_back(val);
}

void Anac::printStrings(void) {
for (auto i = m_strings.begin(); i != m_strings.end(); ++i) {
cout << (*i) << '\n';
}
}

Anac& Anac::getInstance(void) {
if (!singleton) {
#if USED_SHARED
singleton = std::shared_ptr<Anac>(new Anac());
#else
singleton = new Anac();
#endif
}
return *singleton;
}

using namespace std;

int main(int argc, char **argv)
{
Anac::getInstance().addString("hello");
Anac::getInstance().printStrings();
return 0;
}
 
R

Rudi Cilibrasi

I wanted to give my best theory in another post to let an expert
decide if it's right.

THEORY:
It looks like the problem could be in the order of global static
initializers. In other words, if the smart_ptr constructor is called
after the Cmd constructor, it would "zero out" the pointer that was
stored there from the first getInstance() call. This seems to be what
happens and thus it erases the first instance of Anac and replaces it
with a new one.

My detailed questions are as follows:

1) In cases like these where one static initializer calls another but
the reverse is not true and there are no cycles, why doesn't the
compiler do the static initializers in a topologically sorted order?
Wouldn't it make sense for the compiler to sort them all and print a
warning when there is a constructor call cycle among static
initializers?

2) It seems like "simple primitive" types have an advantage over "real
objects" here in that their default constructors happen before any
object constructors due to the way normal executable loaders
initialize a large block of memory to "all zero" bit patterns for
stuff like int within static or global scopes. Doesn't it make sense
for objects to support this "early construction using the all-zero bit
pattern" as well so that they can be made to safely initialize without
any worry of cycles before any objects that require non-zero bit
patterns in the default construction? I was thinking something like a
"zero_default" tag on the default constructor meaning that you cannot
define a method body but it just winds up initializing everything to
all zeros and so is ideal for static initialization. Then these kind
of objects could only contain primitives or other objects that also
have a "zero_default" type default constructor. These would have no
ordering problems and would all happen automatically before any
complex construction. I have found this pattern of construction to be
handy in object-oriented-style C as well in the past. Is there a
clean way to implement it in C++ nowadays?

3) What is the best solution to this problem in general in C++? I
want to retain the modularity of static (non externally visible) data
if possible. What is the best Singleton pattern to use to avoid this
kind of dependence on the order of static construction?

Best regards,

Rudi
 
R

Richard Damon

I wanted to give my best theory in another post to let an expert
decide if it's right.

THEORY:
It looks like the problem could be in the order of global static
initializers. In other words, if the smart_ptr constructor is called
after the Cmd constructor, it would "zero out" the pointer that was
stored there from the first getInstance() call. This seems to be what
happens and thus it erases the first instance of Anac and replaces it
with a new one.

My detailed questions are as follows:

1) In cases like these where one static initializer calls another but
the reverse is not true and there are no cycles, why doesn't the
compiler do the static initializers in a topologically sorted order?
Wouldn't it make sense for the compiler to sort them all and print a
warning when there is a constructor call cycle among static
initializers?

2) It seems like "simple primitive" types have an advantage over "real
objects" here in that their default constructors happen before any
object constructors due to the way normal executable loaders
initialize a large block of memory to "all zero" bit patterns for
stuff like int within static or global scopes. Doesn't it make sense
for objects to support this "early construction using the all-zero bit
pattern" as well so that they can be made to safely initialize without
any worry of cycles before any objects that require non-zero bit
patterns in the default construction? I was thinking something like a
"zero_default" tag on the default constructor meaning that you cannot
define a method body but it just winds up initializing everything to
all zeros and so is ideal for static initialization. Then these kind
of objects could only contain primitives or other objects that also
have a "zero_default" type default constructor. These would have no
ordering problems and would all happen automatically before any
complex construction. I have found this pattern of construction to be
handy in object-oriented-style C as well in the past. Is there a
clean way to implement it in C++ nowadays?

3) What is the best solution to this problem in general in C++? I
want to retain the modularity of static (non externally visible) data
if possible. What is the best Singleton pattern to use to avoid this
kind of dependence on the order of static construction?

Best regards,

Rudi

The order of construction of "Dynamically initialized" (initialized via
a constructor or with a non-constant value) objects is specified by the
standard, to be the order they are declared. It is the programmers
responsibility to make sure that order is acceptable. "Statically
Initialized" object (no constructor and with a constant) happen at load
time.

The authors of the language did not think that every object needed an
initialized flag, that was checked before each access, and if not
initialized, to force the object to be initialized, which is basically
what would be needed to sequence these properly. Note that to detect the
order of usages and detect the issue is akin to the halting problem. In
general, remember that the execution path may go into other translation
units. This makes it impossible to always detect it. Compilers are of
course allowed to warn about it in cases they can detect, as they are
allowed to warn about anything at all, even things that aren't "wrong".
I don't think may detect this as they can only find the simple cases,
and only after a reasonable amount of work. And the easy cases are
probably the ones that the programmer is most apt to find out for
themselves, and some simple rules can eliminate many of them.
 
J

Juha Nieminen

Rudi Cilibrasi said:
for (auto i = m_strings.begin(); i != m_strings.end(); ++i) {

Not related to your question, but since you are using C++11 anyways,
why not save trouble and use: for(auto& str: m_strings) ...
 
C

cartec69

Your theory about the order of static initialization is correct. The fix tothis in your program is to ensure the dynamic initializers happen in the proper order:

static XXXX singleton;

static Cmd c;

Your code actually works fine unchanged in gcc 4.6, since 4.6 adds support for the C++11 constexpr keyword., shared_ptr's default constructor is declared constexpr and evaluated at compile time, so singleton's initialization all happens at load time just as it would for a native pointer. (Order of dynamic initializers doesn't matter if you don't have dynamic initializers.)

Last but not least, unique_ptr is more idiomatically correct than shared_ptr for singletons.
 
R

Rudi Cilibrasi

Thank you for the awesome answers, Cartec! I really appreciate the
guidance and the gcc versions interest me greatly. I have yet to get
deeply into constexpr but it does sound like just what I need! I am
very happy to have made the connection. It will give me a good
context for studying it soon. Cheers,

Rudi
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top