7

Coming from this thread I implemented a similar system in c++ to the chosen solution there.

My problem now is that it is stated there by the user Daniel James that this solution might not work with every compiler (I'm using gcc currently) and is not defined in the c++ standard.

Suppose I have an abstract base-class for the interface and a factory-class as a singleton that stores pointers to a function that constructs the specific classes derived from that interface.

then I have a helper class that looks roughly like this:

base.hpp

...
class implRegistrator {
    public:
        implRegistrator(constructPointer) {
            factory::registerImpl(constructPointer);
        };
}

And an implementation that (through a macro) creates an object of this class to register itself:

impl1.cpp

...
implRegistrator* impl1 = new implRegistrator(getConstructPointer());

How compatible to the C++ standard is this solution? Is it safe to assume that the class instantiation ind impl1.cpp will even happen, since nothing from the main program will actually explicitly call it at compile-time?

Thanks in advance for any answers.

Community
  • 1
  • 1
Ragas
  • 130
  • 2
  • 10
  • 3
    At this point, unfortunately the issue might not be the standardness of the code, but the optimization capabilities of your implementation. You need to make sure that your compiler doesn't optimize out those registration variables that you don't use elsewhere. This bit me once in `.so` vs. `.a` scenarios. – PlasmaHH Apr 29 '14 at 09:08
  • @PlasmaHH An implementation is not allowed to remove any objects which are part of the program. How you specify what is part of the program depends on the implementation, but the definition of a library traditionally means that object files in it only become part of the program if they resolve an undefined external; this is _not_ an optimization, but the way libraries are expected to work. (And FWIW: a `.dll` or a `.so` is _not_ a library; `.so` stands for "shared object", and both behave as object files, not libraries.) – James Kanze Apr 29 '14 at 09:21
  • What is `constructPointer`, and what does `getConstructPointer()` do. There are ways to implement automatic registration code which are standard conformant. – James Kanze Apr 29 '14 at 09:23
  • 3
    @JamesKanze: besides me never claiming they are a library, this "How you specify what is part of the program" is exactly the problem. Most people expect that when they create a variable somewhere and link that translation unit, it will be part of their program. – PlasmaHH Apr 29 '14 at 09:28
  • @PlasmaHH And when you create a variable somewhere and link that translation unit, it will be part of the program. At least with every compiler/linker I know. – James Kanze Apr 29 '14 at 13:31
  • @JamesKanze: I don't know about recent gccs, but in the times of around 4.4 I had the problem that in the standard toolchain the linker was omitting certain object files since it was assuming they were unused. The workaround there was to force it linking everything, as it was removing all unused sections. – PlasmaHH Apr 29 '14 at 13:39
  • @PlasmaHH I don't remember anything like that, but I've not used every version of g++ (and most of the time I've used g++ was under Solaris, where it used the native linker). All the linkers I've actually used have been all or nothing with regards to an object file. – James Kanze Apr 29 '14 at 13:54

5 Answers5

5

So after looking a bit further into the standard at the position where Angew pointed me before, I noticed footnote 34 in [basic.start.init]§4 of the standard that states:

A non-local variable with static storage duration having initialization with side-effects must be initialized even if it is not odr-used (3.2, 3.7.1).

And that actually addresses the problem which is mentioned here. The self registering class is not odr-used and modifies the state of the factory object, thus having an initialization with side-effects.

So per this footnote it is actually safe to do a self registration like the one mentioned by me and Skizz.

Edit: As Matt McNabb mentioned, I shouldn't have relied on the footnote. So here is the part of the specification that the footnote refers to: [Basic.stc.static] §2

If a variable with static storage duration has initialization or a destructor with side effects, it shall not be eliminated even if it appears to be unused, except that a class object or its copy/move may be eliminated as specified in 12.8.

Ragas
  • 130
  • 2
  • 10
  • Footnotes are non-normative – M.M Aug 16 '14 at 22:01
  • @Matt McNabb: But even though they are not normative, they do describe normative behaviour. The Paragraph actually forcing this is [Basic.stc.static] §2 `If a variable with static storage duration has initialization or a destructor with side effects, it shall not be eliminated even if it appears to be unused, except that a class object or its copy/move may be eliminated as specified in 12.8.` Maybe I should have cited this part. – Ragas Aug 21 '14 at 14:13
2

From a standards perspective you can be sure if that translation unit is included in the build. However, as of old there was a problem with Visual C++ static libraries. To be safe I'd use explicit module initializations at the top level of control, or the trick employed by original iostreams implementation, where the header file causes a small internal linkage thing to be initialized, which in turn causes module initialization if not already done.


Oh well I have a question: does anyone remember "Hoare envelopes" module initialization feature, and perhaps direct me to some material? I remember re-searching some years ago, and only hitting my own earlier questions. Frustrating.

Cheers and hth. - Alf
  • 138,963
  • 15
  • 198
  • 315
  • I've never heard of any problem with VC++ in this respect. As far as I know, they've always implemented libraries (at least static libraries) in the normal way. – James Kanze Apr 29 '14 at 09:24
  • @JamesKanze: nice to be able to teach you a new thing then. :) day saved. – Cheers and hth. - Alf Apr 29 '14 at 09:27
  • Hm, how would one go about implementing that trick, is there anywhere you can point me to? But seeing as there is probably no clean way to do this, I think I'll have to explicitly initialize the modules somewhere. – Ragas Apr 29 '14 at 10:21
  • What new thing? There's never been a problem with VC++ with regards to how it processes libraries. It processes them correctly, like just about every other compiler I've used. (About the only thing one could fault them in in this regard is naming the dynamically linked thing a library, when it isn't.) – James Kanze Apr 29 '14 at 13:29
  • @JamesKanze: well it's up to you to learn, or steadfastly asserting that there's nothing to learn, as you're doing. silly strategy that. i'm not going to root up old installation discs to prove things to you. i would probably have to reinstall an old windows version to make it run. – Cheers and hth. - Alf Apr 29 '14 at 14:58
  • Since you can't actually state which version had the problem, and what the problem actually was, it's hard for me to say more. I've used some very old Microsoft C++, and most versions since Visual Studios 2005, but since I've mostly worked on Unix platforms, I've not any real experience with a lot of them. Still, even in the days when everyone was Microsoft bashing, no one seems to have mentioned a problem with the linker. – James Kanze Apr 29 '14 at 15:10
2

No, it is in general not guaranteed that the vairable impl1 will ever be initialised. All the standard says is that a namespace-scope variable is guaranteed to be initialised before the first function defined in the same translation unit (the same .cpp file) is called, or a variable from that translation unit is first used.

The letter of the law is C++11 [basic.start.init]§4:

It is implementation-defined whether the dynamic initialization of a non-local variable with static storage duration is done before the first statement of main. If the initialization is deferred to some point in time after the first statement of main, it shall occur before the first odr-use (3.2) of any function or variable defined in the same translation unit as the variable to be initialized.

So if your impl1.cpp contains the registration variables only, they are not guaranteed to ever be initialised. However, if it contains any functions which will get exectued when your program runs (or a variable referenced from outside), you're guaranteed to have them initialised before any such function is run or variable is referenced.

Angew is no longer proud of SO
  • 161,995
  • 14
  • 331
  • 433
  • So, if I understand you correctly, I'm not guaranteed that this will work unless I directly reference a part of the file from the rest of the program?! Hm, this is discouraging, since that was exatly the purpose of this. Or would it sufficient to have an implicitly called function in that file, like an implementation of a virtual function from base? – Ragas Apr 29 '14 at 10:07
  • @Ragas Good idea about the virtual function body, I am not sure right now. I will look into that one further. – Angew is no longer proud of SO Apr 29 '14 at 10:17
  • Uhm, the "If the initialization is deferred to some point in time after the first statement of main, it shall occur before the first odr-use (3.2) of any function or variable defined in the same translation unit as the variable to be initialized." is in support of dynamically linked libraries, and constitutes the only support for them. They're not mentioned as such, at all. So the conclusion is tad to bleak, but the advice one can draw from that is good anyway. ;-) – Cheers and hth. - Alf Apr 29 '14 at 10:44
  • @Cheersandhth.-Alf Can you elaborate on how the conclusion is too bleak? – Angew is no longer proud of SO Apr 29 '14 at 11:38
  • @Angew: A pure standard C++ program doesn't have dynamic libraries as part of itself, since the standard doesn't support them. The clause about deferred initialization therefore never triggers. In the pedantically formal the possibility it allows could be used, of course, but it's more work for compiler and linker to do that, and like gigabyte `bool` and other truly perverse stuff that's formally allowed it would absolutely not be a selling point, quite the contrary. The chance of encountering that is therefore zero, zilch, nada, nix, and null. Still it's prudent to consider non-conformance. – Cheers and hth. - Alf Apr 29 '14 at 13:49
  • @Cheersandhth.-Alf [This question](http://stackoverflow.com/q/22680757/1782465) demonstrates that the chance is in fact strictly positive. – Angew is no longer proud of SO Apr 29 '14 at 14:52
  • @Angew: is so then you should have no problem producing a *concrete example*, like, a single one would suffice. – Cheers and hth. - Alf Apr 29 '14 at 14:56
  • @Cheersandhth.-Alf Er? Did you look at the question I linked to in my previous comment? – Angew is no longer proud of SO Apr 29 '14 at 14:58
  • @Angew: No, but I looked now. That's a question without a complete example and with an "obvious" (but rather far-fetched) explanation asserted by the OP, and uncritically adopted in all answers. Apparently he's run into the global initialization order fiasco problem. Or, it might have been a bug, that's been fixed in VC 12.0. But I don't think so. – Cheers and hth. - Alf Apr 29 '14 at 15:14
  • 1
    @Angew: Thanks to your Comment I just found the Answer to the original question. The position you pointed me to was already extremely good. We only missed footnote `34`: `A non-local variable with static storage duration having initialization with side-effects must be initialized even if it is not odr-used (3.2, 3.7.1).` – Ragas Aug 16 '14 at 21:52
2

I see two problems with the code. Firstly, you're using dynamic allocation and secondly, you're using function pointers. Here is my solution:-

    #include <iostream>
    #include <string>
    #include <map>

    class FactoryBase
    {
    protected:
        FactoryBase (const std::string &name)
        {
            m_factory_items [name] = this;
        }

    public:
        virtual ~FactoryBase ()
        {
        }

        template <class T> static T *Create ()
        {
            return static_cast <T *> (m_factory_items [T::Name]->Create ());
        }

    private:
        virtual void *Create () = 0;

    private:
        static std::map <const std::string, FactoryBase *>
            m_factory_items;
    };

    std::map <const std::string, FactoryBase *>
        FactoryBase::m_factory_items;

    template <class T>
        class FactoryItem : public FactoryBase
    {
    public:
        FactoryItem () :
            FactoryBase (T::Name)
        {
            std::cout << "Registering class: " << T::Name << std::endl;
        }

        virtual ~FactoryItem ()
        {
        }

    private:
        virtual void *Create ()
        {
            return new T;
        }
    };

    class A
    {
    public:
        A ()
        {
            std::cout << "Creating A" << std::endl;
        }

        virtual ~A ()
        {
            std::cout << "Deleting A" << std::endl;
        }

        static const std::string
            Name;

    private:
        static FactoryItem <A>
            m_registration;
    };

    const std::string
        A::Name ("A");

    FactoryItem <A>
        A::m_registration;

    class B
    {
    public:
        B ()
        {
            std::cout << "Creating B" << std::endl;
        }

        virtual ~B ()
        {
            std::cout << "Deleting B" << std::endl;
        }

        static const std::string
            Name;

    private:
        static FactoryItem <B>
            m_registration;
    };

    const std::string
        B::Name ("B");

    FactoryItem <B>
        B::m_registration;

    int main (int argc, char *argv [])
    {
        A
            *item_a = FactoryBase::Create <A> ();

        B
            *item_b = FactoryBase::Create <B> ();

        delete item_a;
        delete item_b;
    }

There's no error checking in the Create function, but I'll leave that as an exercise for the reader.

Skizz
  • 67,359
  • 10
  • 66
  • 108
  • Hm, at least as far as I can see right now, this code doesn't solve the problem. In your code you still specify the type in the main function. Could this be done without that just by an identifier given in runtime? Of course, like this the other problems won't arise. – Ragas Apr 29 '14 at 14:41
  • You can add a `Create(std::string name)` to FactoryBase which searches using the `name` parameter rather than `T::Name`. You could make it templatised like I have or just have it return a `void *` and the caller does the cast, up to you really. There is no reason why A and B can't have the same base class. – Skizz Apr 29 '14 at 16:34
  • Ok so I looked into this a little bit more and I see how your solution is somewhat more elegant. But it is not safer from a c++ standards point of view, is it? It's still up to the compiler if it initializes class A, when I just call it through the name-string?! – Ragas May 04 '14 at 15:20
  • 1
    @Ragas: The thing about this method is that there is no dynamic initialisation, that is, none through the use of the `new` keyword. This code uses static initialisation (constructors called on statically allocated objects). I'm not an expert on the C++ standard, but this has never caused issues in practice for me. This is one of those cases that make my brain hurt and all I can assume is that the compiler/linker is not allowed to remove unreferenced static objects. PODs it could remove. – Skizz May 06 '14 at 09:23
  • Thanks, you helped me a lot with this. – Ragas May 06 '14 at 11:57
  • And this won't suffer from the "static initialization order fiasco"? – paulm Mar 16 '15 at 07:40
  • 3
    @paulm: Three years too late, but... you are correct, it does suffer from SIOF. If a static object somewhere is created that calls FactoryBase::Create<>, it will potentially crash since the map might not exist yet. The solution is to wrap the map as a static local in a static function. – Chris Laplante Feb 24 '18 at 01:28
1

Here's an implementation of this factory pattern that tackles this specific issue.

It makes sure the factory singleton implementation is the one that calls the registrar methods on construction. Which means that, if the factory is used the registration will happen.

Sergio Basurco
  • 3,088
  • 2
  • 19
  • 40