Confusing difference in raw versus shared_ptr behavior w/ static initializers

Discussion in 'C++' started by Rudi Cilibrasi, Nov 2, 2011.

  1. 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;
    }
    Rudi Cilibrasi, Nov 2, 2011
    #1
    1. Advertising

  2. 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
    Rudi Cilibrasi, Nov 3, 2011
    #2
    1. Advertising

  3. Re: Confusing difference in raw versus shared_ptr behavior w/ staticinitializers

    On 11/2/11 8:43 PM, Rudi Cilibrasi wrote:
    > 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.
    Richard Damon, Nov 3, 2011
    #3
  4. Rudi Cilibrasi <> wrote:
    > 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) ...
    Juha Nieminen, Nov 3, 2011
    #4
  5. Rudi Cilibrasi

    zindorsky Guest

    On Nov 3, 12:48 am, Juha Nieminen <> wrote:
    > Rudi Cilibrasi <> wrote:
    > >  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) ...


    Because range-based for is not supported in GCC 4.5 (the compiler the
    OP is using).
    http://gcc.gnu.org/projects/cxx0x.html
    zindorsky, Nov 3, 2011
    #5
  6. Rudi Cilibrasi

    Guest

    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.
    , Nov 4, 2011
    #6
  7. 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

    On Nov 4, 4:13 pm, wrote:
    > Your theory about the order of static initialization is correct. The fix to this in your program is to ensure the dynamic initializers happen in theproper 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 ofdynamic 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.
    Rudi Cilibrasi, Nov 6, 2011
    #7
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. j l
    Replies:
    5
    Views:
    384
    A. Bolmarcich
    Feb 23, 2004
  2. James Robert Leek

    JNI Invocation: static UN-initializers?

    James Robert Leek, Aug 31, 2004, in forum: Java
    Replies:
    2
    Views:
    478
    James Robert Leek
    Aug 31, 2004
  3. HK
    Replies:
    7
    Views:
    408
    Wibble
    May 30, 2005
  4. Colin Caughie
    Replies:
    1
    Views:
    694
    Shooting
    Aug 29, 2006
  5. Paul Butcher
    Replies:
    12
    Views:
    692
    Gary Wright
    Nov 28, 2007
Loading...

Share This Page