C++ how safe are self registering classes?

强颜欢笑 提交于 2020-01-01 10:12:19

问题


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.


回答1:


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.




回答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.




回答3:


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.




回答4:


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.




回答5:


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.



来源:https://stackoverflow.com/questions/23360196/c-how-safe-are-self-registering-classes

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!