2

I have a class defined in the same file of main, another class(full of static functions/members) defined in 2 seperate files, and it crashes. I guess this may be relevant to the lifetime of global/static instance. it seems that in the ctor, the static member has not been initialized, and it may happends that when exit, the static member is freed before the first instance is destructed. here is the test code:

    //testh.h
    #include <map>
    class Sc {
    public:
        static void insert();
        static void out();

    private:
        static std::map<int, int> map_;
    };

    //testcpp.cpp
    #include "testh.h"
    #include <iostream>
    std::map<int, int> Sc::map_;

    void Sc::insert() {
        map_.insert(std::make_pair(2,3));
    }

    void Sc::out() {
        for(auto m : map_) {
            std::cout << m.first << ' ' << m.second << '\n';
        }
    }

    //main.cpp
    #include "testh.h"
    class Nc {
    public:
        Nc() {
            Sc::insert();
            Sc::out();
        }
        ~Nc() {
            Sc::insert();
            Sc::out();
        }
    };

    Nc nc;
    int main() {

        system("pause");
        return 0;
    }

here are some strange behaviours of the above code:

if I replace the staic member to int, it will not crash, so I suppose there may be problems with std::map?

if I put all the codes into main.cpp, it will not crash, but wouldn't these generates the same codes?

how to solve this problem if I don't want to do dynamic allocation(new)?

Frahm
  • 805
  • 2
  • 9
  • 16

1 Answers1

8

The problem is that you do not know what order th global variables will be constructed in:

This

// test.cpp
std::map<int, int> Sc::map_;

And this

//main.cpp
Nc nc;

Because these are in different compilation units the standard does not guarantee the order they will be created in. Thus if nc is created first then any attempt to use Sc::map_ will fail (and nc does use this via its calls to the statics).

When you put the globals into one file:

//main.cpp
std::map<int, int> Sc::map_;
Nc nc;

Then the order is guaranteed. It is the order of declaration. So as long as you put Sc::map_ first then it will all work.

There is a simple technique to get around this::

Replace this:

private:
    static std::map<int, int> map_;
};

With:

private:
    static std::map<int, int>& getMap()
    {
        static std::map<int, int> instance;
        return instance;
    }
};

But the real problem is that you are using global mutable state (global variables). Try not to use them at all. It tightly binds your code to the globl state. You should be passing state to methods with parameters or via objects that know how to retrieve the state.

Martin York
  • 257,169
  • 86
  • 333
  • 562
  • so is there any way to make sure the static member is destroyed at the end of the process? – Frahm Mar 06 '13 at 08:40
  • static members are always correctly destroyed. "Static Storage Duration" objects are destroyed in the reverse order of creation after main exits. – Martin York Mar 06 '13 at 08:41
  • @BЈовић: Yep. As usual parashift is completely useless. As it does not tell you how to solve the problem correctly. I completely hate that website as it is full of subtle mistakes and misinformation. There solution to this problem is crap (it leaks). – Martin York Mar 06 '13 at 08:46
  • @LokiAstari [it does](http://www.parashift.com/c++-faq/static-init-order-on-first-use.html) – BЈовић Mar 06 '13 at 08:48
  • @BЈовић: Its a bad solution. As it leaks. Thus in my opinion wrong and not a solution. – Martin York Mar 06 '13 at 08:48
  • @BЈовић: Another great one: http://www.parashift.com/c++-faq/placement-new.html can you even spot the error here. – Martin York Mar 06 '13 at 08:54
  • Thanks for the solution, another question, will the dtor always be called safely in my situation? if not, should I write a particular method to do the static call before exit? – Frahm Mar 06 '13 at 08:57
  • @LokiAstari Oh, you are right about the leak. At the placement new link, I do not see anything wrong. Can you point it out? – BЈовић Mar 06 '13 at 09:21
  • @BЈовић: The use of `char memory[sizeof(Fred)]` is not guaranteed to be aligned for anything but char. That is pointed out in the danger section. But there is an easy solution. By using `memory = new char[sizeof(Fred)]` you get memory that is guaranteed to be aligned correctly for any object of `sizeof(Fred)` (or smaller). So now we have a memory leak problem. By just using RAII principles we wrap the `new`. `std::vector buffer(sizeof(Fred));char* memory = &buffer[0];`. The point here is that (generally) placement new should be used with a dynamically allocated buffer for alignment. – Martin York Mar 06 '13 at 15:31
  • @BЈовић: My point is: parashift has lots of info (lots of it good). But sometimes the criticisms are overblown as the standard techniques for doing stuff in C++ guide you around these problems (they may be gotchas for beginners learning by themselves but any experienced developer is not going to fall into these trap as that is not how you write C++). Also I find a lot of the code is suspect (not all). But I found two quickly last night (I had seen the placement new one before). But it took me a second to spot the leak. – Martin York Mar 06 '13 at 15:40