2

After digging the web, I found some reference to a powerful pattern which exploits CRTP to allow instantiation at run-time of static members:

C++: Compiling unused classes

Initialization class for other classes - C++

And so on. The proposed approach works well, unless such class hierarchy is placed into an external library. Doing so, run-time initialization no more works, unless I manually #include somewhere the header file of derived classes. However, this defeats my main purpose - having the change to add new commands to my application without the need of changing other source files.

Some code, hoping it helps:

class CAction
{
protected:
    // some non relevant stuff
public:
    // some other public API
    CAction(void) {}
    virtual ~CAction(void) {}

    virtual std::wstring Name() const = 0;
};

template <class TAction>
class CCRTPAction : public CAction
{
public:
    static bool m_bForceRegistration;
    CCRTPAction(void) { m_bForceRegistration; }
    ~CCRTPAction(void) { }

    static bool init() {
        CActionManager::Instance()->Add(std::shared_ptr<CAction>(new TAction));
        return true;
    }
};

template<class TAction> bool CCRTPAction<TAction>::m_bForceRegistration = CCRTPAction<TAction>::init();

Implementations being done this way:

class CDummyAction : public CCRTPAction<CDummyAction>
{
public:
    CDummyAction() { }
    ~CDummyAction() { }
    std::wstring Name() const { return L"Dummy"; }
};

Finally, here is the container class API:

class CActionManager
{
private:
    CActionManager(void);
    ~CActionManager(void);
    std::vector<std::shared_ptr<CAction>> m_vActions;   
    static CActionManager* instance;
public:
    void Add(std::shared_ptr<CAction>& Action);
    const std::vector<std::shared_ptr<CAction>>& AvailableActions() const;
    static CActionManager* Instance() {
        if (nullptr == instance) {
            instance = new CActionManager();
        }
        return instance;
    }
};

Everything works fine in a single project solution. However, if I place the above code in a separate .lib, the magic somehow breaks and the implementation classes (DummyAction and so on) are no longer instantiated.

I see that #include "DummyAction.h" somewhere, either in my library or in the main project makes things work, but

  1. For our project, it is mandatory that adding Actions does not require changes in other files.
  2. I don't really understand what's happening behind the scene, and this makes me uncomfortable. I really hate depending on solutions I don't fully master, since a bug could get out anywhere, anytime, possibly one day before shipping our software to the customer :)
  3. Even stranger, putting the #include directive but not defining constructor/destructor in the header file still breaks the magic.

Thanks all for attention. I really hope someone is able to shed some light...

Community
  • 1
  • 1
Marco Veglio
  • 332
  • 1
  • 8
  • @Tony The Lion I can see legit use-cases of this! Generally it should be avoided, but sometimes it's the best option. And the above code does not violate good software writing practices. – Ralph Tandetzky Jul 29 '13 at 10:33
  • 1
    I only know of one legit singleton use case, that is a logger type object. I know of no legit use case for manager classes, that's just plain bad design. – Tony The Lion Jul 29 '13 at 10:36
  • @RalphTandetzky it uses Singletons, "let's try expanding this code... oh, can't without rewriting most of it". It uses classes named 'manager'... and it does what? Manage? what is that exactly? What do I need this class for? – thecoshman Jul 29 '13 at 10:36
  • 1
    This is a perfectly valid question - the singleton debate belongs in chat. – willj Jul 29 '13 at 11:27
  • I believe this occurs because the linker thinks the code is not referenced. Which compiler are you using? You could try some different compiler options to test this hypothesis: http://stackoverflow.com/questions/16294842/how-to-disable-c-dead-code-stripping-in-xcode – willj Jul 29 '13 at 11:30
  • Compiler is MSVC++12. The objects are not required to be singleton (debate is still ongoing, we are at the very beginning of the design phase and I tried to lay out some code to bring out unforeseen issues), what is required - by now - is that at least one instance can be accessed by a single central repository, without the need for the repository to know all the classes and allowing the coders to just add the .h/.cpp files without need to modify other code. – Marco Veglio Jul 29 '13 at 12:08
  • 1
    Have you tried using a .dll instead of a .lib? I remember it "fixed" the problem for me a while ago, although I cannot confirm it is reliable. According to [this answer](http://stackoverflow.com/a/78235/420683) it is reliable. – dyp Jul 29 '13 at 13:19
  • Thanks for pointing it out. I'd like to avoid dlls, since the code will make heavy use of template code and API is goingo to be quite complex. However, I'll keep it in mind. – Marco Veglio Jul 29 '13 at 15:48
  • 1
    A DLL linked at load-time (via a dummy .lib) is used like a static .lib, no changes are required IIRC. (As opposed to run-time linking via `LoadModule`, `GetProcAddress` etc.) – dyp Jul 29 '13 at 16:56

3 Answers3

4

I can describe the cause of the problem; unfortunately I can't offer a solution.

The problem is that initialisation of a variable with static storage duration may be deferred until any time before the first use of something defined in the same translation unit. If your program never uses anything in the same translation unit as CCRTPAction<CDummyAction>::m_bForceRegistration, then that variable may never be initialised.

As you found, including the header in the translation unit that defines main will force it to be initialised at some point before the start of main; but of course that solution won't meet your first requirement. My usual solution to the problems of initialising static data across multiple translation units is to avoid static data altogether (and the Singleton anti-pattern doubly so, although that's the least of your problems here).

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • Thanks for clarification, Mike. I'm ashamed I cannot vote up your answer, since it has been of great help, my reputation score is still too low ... – Marco Veglio Jul 30 '13 at 13:41
1

As explained in Mike's answer, the compiler determines that the static member CCRTPAction<CDummyAction>::m_bForceRegistration is never used, and therefore does not need to be initialised.

The problem you're trying to solve is to initialise a set of 'plugin' modules without having to #include their code in a central location. CTRP and templates will not help you here. I'm not aware of a (portable) way in C++ to generate code to initialise a set of plugin modules that are not referenced from main().

If you're willing to make the (reasonable) concession of having to list the plugin modules in a central location (without including their headers), there's a simple solution. I believe this is one of those extremely rare cases where a function-scope extern declaration is useful. You may consider this a dirty hack, but when there's no other way, a dirty hack becomes an elegant solution ;).

This code compiles to the main executable:

core/module.h

template<void (*init)()>
struct Module
{
    Module()
    {
        init();
    }
};

// generates: extern void initDummy(); Module<initDummy> DummyInstance
#define MODULE_INSTANCE(name) \
    extern void init ## name(); \
    Module<init ## name> name ## Instance

core/action.h

struct Action // an abstract action
{
};

void addAction(Action& action); // adds the abstract action to a list

main.cpp

#include "core/module.h"

int main()
{
    MODULE_INSTANCE(Dummy);
}

This code implements the Dummy module and compiles to a separate library:

dummy/action.h

#include "core/action.h"

struct DummyAction : Action // a concrete action
{
};

dummy/init.cpp

#include "action.h"

void initDummy()
{
    addAction(*new DummyAction());
}

If you wanted to go further (this part is not portable) you could write a separate program to generate a list of MODULE_INSTANCE calls, one for each module in your application, and output a generated header file:

generated/init.h

#include "core/module.h"

#define MODULE_INSTANCES \
    MODULE_INSTANCE(Module1); \
    MODULE_INSTANCE(Module2); \
    MODULE_INSTANCE(Module3);

Add this as a pre-build step, and core/main.cpp becomes:

#include "generated/init.h"

int main()
{
    MODULE_INSTANCES
}

If you later decide to load some or all of these modules dynamically, you can use exactly the same pattern to dynamically load, initialise and unload a dll. Please note that the following example is windows-specific, untested and does not handle errors:

core/dynamicmodule.h

struct DynamicModule
{
    HMODULE dll;

    DynamicModule(const char* filename, const char* init)
    {
        dll = LoadLibrary(filename);
        FARPROC function = GetProcAddress(dll, init);
        function();
    }
    ~DynamicModule()
    {
        FreeLibrary(dll);
    }
};

#define DYNAMICMODULE_INSTANCE(name) \
    DynamicModule name ## Instance = DynamicModule(#name ".dll", "init" #name)
willj
  • 2,991
  • 12
  • 24
  • Thanks for your patience @willj, your proposal is working and I'm going to accept your answer. Just one more clarification - why is the MODULE_INSTANCES snippet not portable? – Marco Veglio Jul 30 '13 at 13:26
  • 1
    Writing a separate program and adding it as a pre-build step would require some platform/build-system specific changes. Perhaps it would be better to say that it's not pure C++. – willj Jul 30 '13 at 13:30
1

As Mike Seymour stated the static template stuff will not give you the dynamic loading facilities you want. You could load your modules dynamically as plug ins. Put dlls containing an action each into the working directory of the application and load these dlls dynamically at run-time. This way you will not have to change your source code in order to use different or new implementations of CAction.

Some frameworks make it easy to load custom plug ins, for example Qt.

Ralph Tandetzky
  • 22,780
  • 11
  • 73
  • 120
  • What about a single dll containing all the actions? BTW the actions are going to make use of template code, I found that exposing templates on a dll inteface is less than ideal. Is it going to work also with static libraries? – Marco Veglio Jul 29 '13 at 12:51
  • 1
    A single dll containing all actions will do as well. And yes templates and dlls don't work well at all. Avoid it. It will probably work with static libraries, if you make sure the classes get instantiated somewhere within the static library. Defining member functions such as constructors and destructors seems to work for that purpose. – Ralph Tandetzky Jul 29 '13 at 13:36
  • 1
    templates and dlls work fine, provided that you build all the dlls from exactly the same source. If you can do that, the remaining complication is that global static objects are not shared between dlls. – willj Jul 30 '13 at 09:35
  • this is likely, but not guaranteed for the future. Therefore, I would better follow willj's advice by now. Thanks all for pointing out this anyway, it will likely help in further phases of the project. By the way, what about dynamically allocated resources passed across DLL boundaries? I know that this can also be an issue, is it mitigated by having the whole solution come from the same source? – Marco Veglio Jul 30 '13 at 13:44