Any alternatives to automatic class instantiation via macro?

Started by
12 comments, last by WoopsASword 8 years, 2 months ago

Hey! So before I explain what I'm doing, I'd like to first off say I got this idea from the Urho3D Engine http://urho3d.github.io/

Basically, they have the Application class as the core of their engine, and you inherit your own application from that class. You then implement your own versions of the relevant methods. There is then a macro that is called that you pass your app's class name to which essentially creates an object of your class for you and does all the internal initialization. So in other words, you have zero interaction with the internals of the engine. You don't need to explicitly call any functions to start up your app. The macro takes care of all of that.

Simplified version:


#define APPLICATION_MAIN(applicationClassName)\
int ApplicationMain(int argc, char **ppArgv)\
{\
    applicationClassName application(...);\
    return application.Run();\
}

And the application of this:

class MyApp : public Application
{
};

APPLICATION_MAIN(MyApp)

The alternative to this as far as I can tell is having the user manually instantiate their class inside of some designated platform-independent main function that gets called from the platform's main function.

Eg:


int SomeCustomMain(int argc, char **ppArgv)
{
    MyAppClass myApp(...);

    return myApp.Run();
}

I like the idea of the user not having to directly instantiate their class (since it's done indirectly via the macro), but I am wondering if there is a way to accomplish this same feature without using a macro. I have yet to come up with anything. This is using the preprocessor to manipulate the source to use your derived class name instead of the base class name. And that's not possible via any language features, so I don't know.

Any ideas?

Advertisement

Templates can get you most of the way there, but you can't literally define a new function at compile time with a C++ template (as far as I know). I'm skeptical of how useful this pseudo-DI thing is in practice though. You're only saving a few lines of boiler-plate, unless you want to extend it for a whole bunch of other dependencies, at which point you should probably look for some kind of DI framework.

Templates can get you most of the way there, but you can't literally define a new function at compile time with a C++ template (as far as I know). I'm skeptical of how useful this pseudo-DI thing is in practice though. You're only saving a few lines of boiler-plate, unless you want to extend it for a whole bunch of other dependencies, at which point you should probably look for some kind of DI framework.

Yeah, I considered using templates, but it still wouldn't be possible to do this exactly using them. I just really liked the idea of just simply extending a built-in class and leaving it at that. You don't need declare any objects/call any functions. Perhaps I am being too user friendly, though. I don't know. I just wanted to get some opinions on it. It's not a deal-breaking feature I need to have by any means.


Perhaps I am being too user friendly, though. I don't know.

I'd call it supremely user-unfriendly, for many users. And I'd heavily advocate for giving users the power to own their main() function.

SDL used to hijack main() with a macro on Mac and Windows. Nothing but trouble, in my experience - especially if you actually needed to customise startup behaviour in any non-trivial way.

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]


SDL used to hijack main() with a macro on Mac and Windows.

They still do on Windows. It's called SDL_main (the macro), I believe.

Basically, they have the Application class as the core of their engine, and you inherit your own application from that class


I increasingly consider this design an anti-pattern. It leads to an inheritance explosion for more complex games that are composed of multiple tools apps, among other things; the core game engine ends up with its GameApplication and then the game editor code, which uses game code, ends up with EditorApplication:GameApplication, and then the separate particle and level editor tools end up with LevelEditorApplication:EditorApplication, and so on.

This isn't just hypothetical. This is actual crap I have to deal with in our engine whenever I'm forced to delve into the application-specific bits or the application management code and have to deal with trying to figure out the difference between CoreApplication and ScriptableApplication and GameApplication and GameClientApplication and so on and so forth. Pain in the butt, especially when someone decides to put something in ScriptableApplication that you later realize needed to be in CoreApplication or just defaults to putting crap in GameApplication that really only needed to be in ToolApplication.

It's a mess. It's inheritance. It's a classical example of abuse of the is-a pattern where the has-a compositional approach makes more sense. Have a core "application loop" class if you wish. But make it composable. That whole "entity system" nonsense that everyone hypes these days? It's just an expression of composition! It's a general pattern, not some magical tool that only works for entities. Your engine is a composition of systems. Your application is a composition of states. Your resources are compositions of data. Your entire level is "just" a composition of entities. etc.

Rather than an Application base class, prefer to use dependency injection or other configuration to allow each individual application to setup the core loop as desired. e.g.

// StateMachine.h
class StateMachine {...};
class IState {...};

// Application.h
class IState;
class Application {
  Application(int argc, char** argv, IState& initialState);
  ...
};

// Engine.h
class Engine {...};

// GameClient.cpp
#include "StateMachine.h"
#include "Application.h"
#include "Engine.h"

class GameMainState : IState {...};

int main(int argc, char** argv)
{
  Engine engine;
  GameMainState state(engine);
  Application app(argc, argv, state);

  return app.runForever();
}

// LevelEditor.cpp
class LevelEditorMainState : IState {...};

int main(int argc, char** argv)
{
  Engine engine;
  Editor editor(engine);
  LevelEditorMainState state(editor, engine);
  Application app(argc, argv, state);

  return app.runForever();
}

// WeirdNetworkUtilityThingy.cpp
class WeirdNetworkUtilityThingyMainState : IState {...};

int main(int argc, char** argv) {
  WeirdNetworkUtilityThingyMainState state;
  Application app(argc, argv, state);

  while (wait_on_third_party_net_io_library) {
    // do special stuff that doesn't fit into the Application loop's model very well
    blargh();

    if (!app.runOnce())
      return 1;
  }

  return app.resultCode();
}

If you feel that users are going to be creating a new application so frequently that you need to save them the 90 seconds of cut-n-pasting the 6 lines of boilerplate from your documentation, make a macro, but you should feel dirty doing it. tongue.png

Sean Middleditch – Game Systems Engineer – Join my team!

I'm going to cast a vote in favor of letting the user have control of the lifetime of the objects. Magic systems that purport to make life "simpler" usually backfire.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Thank you everyone for the replies.

@Sean I agree. I can see how inheriting everything from some base app class can cause issues and make the code much harder to maintain. Although I don't believe my project will reach that amount of complexity anytime soon, no sense in writing something I'll have difficulty in expanding on later.

And generally, I do tend to favor composition over inheritance, I just saw how Urho3D did it and it sparked my interest. Nonetheless, I'll be doing some refactoring most likely.

Thank you again for your guys' input!

If you're keen on it, put it into a "helper"/"utility" layer that's optional. It only saves you 3 lines of code, once in thr entire application -- macros are generally used where they'll be paying off more than that.

Also, I'd personally find it more idiomatic for thr "app" object to be executed via operator() rather than a named method, and would call the macro something like:
#define DECLARE_MAIN(T)\
int main(int argc, char **argv){\
    T app(argc, argv);\
    return app();
}
If you're porting to a bunch of different platforms, which all use non-standard names for the main function, then it might be handy to have a wrapper for "main". This is common on engines that need to run in different environments, such as Java, .NET, etc as well as "native apps" -- i.e. on some platforms you can't write your main function in C/C++!
In that situation, you'd write a standard main for every platform which then calls out to some forward-declared but non-existent custom-main function that the user can implement.

Alternatively, if you want to force the user to use a "main object" like in your example, you could use your macro, or force the user to define it as a compiler -D option, e.g. your engine might include:
int main(int argc, char **argv){
#ifndef MAIN_CLASS
#error "You must define MAIN_CLASS when compiling the engine"
#endif
    MAIN_CLASS app(argc, argv);
    return app();
}
Or you can simply declare the main class but not implement it:
//app.h
class Main { public:
  Main( int argc, char** argv);
  int Run();
void* userdata;
};
//Engine.cpp
int main(int argc, char **argv){
    Main app(argc, argv);
    return app.Run();
}

//MyExe.cpp
#include "app.h"
Main::Main( int argc, char** argv) { userdata = 0; }
int Main::Run()
{ return 42; }
But I agree with others that it's better to let the app write it's own main function...

I agree with everyone... on desktop.

Unfortunately Android and iOS came to crash the party where there is no main, and the former enters into Native Code via a Java loader that loads an so library with a set of arbitrary-named JNI function calls, and the latter enters the system by overloading AppDelegate.

Considering these two bastards if they need to be supported, the macro idea looks suddenly more appealing; although I personally still prefer letting the user write these JNI loaders or iOS AppDelegates himself, instead of trying to do it for him (specially when the user needs to release resources or be notified of low memory conditions).

If a macro tries to do it for the user, when something goes wrong there's always that weird feeling that it's the macro's overloaded method fault (i.e. "I bet main system isn't informing me of low memory conditions even though the app is receiving them")

This topic is closed to new replies.

Advertisement