1
\$\begingroup\$

From the book Game Engine Architecture

In C++, global and static objects are constructed before the program’s entry point (main(), or WinMain() under Windows) is called. However, these constructors are called in a totally unpredictable order. The destructors of global and static class instances are called after main() returns, and once again they are called in an unpredictable order. Clearly this behavior is not desirable for initializing and shutting down the subsystems of a game engine, or indeed any software system that has interdependencies between its global objects.

The book suggests to implement the subsystems as singelton objects with empty constructor and destructor and add explicit Init() and Dispose() methods, such that the construction and destruction order can be manually specified. The question is now whether this can be done using global static classes instead of singelton objects, like this:

//Renderer.h
class Renderer
{
public:
    static void Init()
    {
        //init logic
    }

    //Some static, global fields

    static void Dispose()
    {
        //Disposing logic
    }
private:
     //some more static, not global fields / methods
};

//Engine.h
class Engine
{
public:
  static void Init()
  {
    Renderer::Init();
    //etc
  }
  //other stuff

  static void Dispose()
  {
      Renderer::Dispose();
      //etc
  }
};

//Main.cpp

int main()
{
    Engine::Init();
    //some work
    Engine::Dispose();
}

Will this kind of approach also solve the initialization order problem described in the book?

\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

Will this kind of approach also solve the initialization order problem described in the book?

Yes.

The book is referring to the "static initialization order fiasco," which has to do with static objects. In your example you are creating static functions, which don't get "initialized" ever; they aren't objects. So they are not subject to this issue.

The static members of these classes that you add will be objects and will be subject to the SIOF. But as long as you don't allow them to do any interdependent initialization, as long as you defer that to your Init function (and the cleanup to your Dispose function), which you call manually, you can use this approach to solve the problem.

(Note that your "static class" approach is essentially the equivalent to a collection of free functions and global state.)

\$\endgroup\$
2
  • \$\begingroup\$ Thank you for the clarification! As a question: If I plan to only have one instance of the engine and all it's subsystems anyways, do you recommend that I have all the subsystems as regular objects, or as singeltons or as static functions like I described above or as free functions? The book recommends the singelton pattern with Init() and Dispose() methods. But why use classes at all when the singelton object is global and only exists once anyways? \$\endgroup\$
    – Stefan B
    Apr 9, 2018 at 20:49
  • \$\begingroup\$ I'd build the engine and the subsystems as regular classes anyway, and "just" create only one if I needed only one. Globally, if you must (although you rarely need to). I would recommend against singletons specifically, and any other form of premature pessimization. \$\endgroup\$
    – user1430
    Apr 9, 2018 at 20:56
0
\$\begingroup\$
  1. A word about static objects.
  2. Static functions approach.

A word about static objects.

First of all, the book is not perfectly correct about static objects: The standard does not guarantee that they will be constructed before the program's entry point. It only guarantees that they are constructed before their first usage.

In practice, we can note that:

  • Global static object are constructed before the program's entry point,
  • Static field (class variables) objects or static variable (method variables) objects are constructed when they are used.

This is useful, for instance, to simulate the concept of static constructor, available in C#.

Let's consider the following code...

#include <iostream>

struct Bar
{
    Bar ()
    {
        std::cout << "Bar constructor\n";
    }
};

class Foo
{
public:
    Foo ()
    {
        static StaticConstructor staticConstructor;
        std::cout << "Foo constructor\n";
    }

private:
    struct StaticConstructor
    {
        StaticConstructor ()
        {
            std::cout << "Foo static constructor\n";
        }
    };

    static Bar field;
};

int main ()
{
    std::cout << "Let's go!\n";
    Foo foo1;
    Foo foo2;
    return EXIT_SUCCESS;
}

Output:

Let's go!
Foo static constructor
Foo constructor
Foo constructor

You can note that the static variable staticConstructor is constructed after "Let's go!", so after the program's entry point. You can also note that the static field field is never constructed because it was not used.

Static functions approach.

Yes, this approach is the correct one.

That is what the book recommended. A singleton is nothing less than a global variable (that also guarantees to be instantiated only once). Methods of a singleton can be seen as free methods... except that they can share data in the protected namespace of the singleton class. But if you do not have such data, it's all the same. ^^

The only problem with that, is that you are not exception proof: What you initialize this way is not guaranteed to be disposed. Have a look at the RAII principles.

What I could suggest is to add some Subsytems class responsible of both Init and Dispose sequences.

Here is a small example of what I mean:

#include <iostream>

class Engine
{
public:
    static void Init ()    { std::cout << "Engine::Init\n"; }
    static void Dispose () { std::cout << "Engine::Dispose\n"; }
};

class Renderer
{
public:
    //static void Init ()    { throw "Renderer init failed!"; }
    static void Init ()    { std::cout << "Renderer::Init\n"; }
    static void Dispose () { std::cout << "Renderer::Dispose\n"; }
};

class Subsystems
{
public:
    Subsystems () {}

    void Init ()
    {
        Engine::Init();
        Renderer::Init ();
    }

    ~Subsystems ()
    {
        Renderer::Dispose ();
        Engine::Dispose();
    }
};

int main ()
{
    std::cout << "Let's go!\n";
    try
    {
        Subsystems subsystems;
        subsystems.Init ();
        std::cout << "If init worked, do some stuff...\n";
    }
    catch ( ... )
    {
        std::cout << "Kaboom!\n";
    }
    return EXIT_SUCCESS;
}

Output:

Let's go!
Engine::Init
Renderer::Init
If init worked, do some stuff...
Renderer::Dispose
Engine::Dispose

If you uncomment the Init with the throw and comment the other one, you'll have:

Let's go!
Engine::Init
Renderer::Dispose
Engine::Dispose
Kaboom!

Of course, your Dispose methods should be designed to be robust against a call where partial or no initialization has been performed. ;)

Also note that I didn't add the init sequence in the Subsystems constructor. Why? Because if a constructor throws, the destructor is not called.

Hope it helps. ;)

\$\endgroup\$
1
  • \$\begingroup\$ Thank you for the response. I completely understand what you mean. But I have a question. You use the approach of having a Subsystems class which calls the init and dispose method of all other subsystems. Instead of using classes for the subsystems, why not implement them as free functions, where each subsystem is in its own .cpp/.h file and then have a Subsystems.cpp file which instances them as free methods. Is there a reasons to use classes at all in terms of usability or is this just preference? \$\endgroup\$
    – Stefan B
    Apr 9, 2018 at 22:40

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .