C ++自注册类的安全性如何?

时间:2014-04-29 09:04:47

标签: c++ class gcc standards-compliance

来自this thread我在c ++中使用类似的系统实现了所选择的解决方案。

我现在的问题是用户Daniel James在那里说这个解决方案可能不适用于每个编译器(我当前正在使用gcc)并且没有在c ++标准中定义。

假设我有一个接口的抽象基类和一个工厂类作为单例,它存储指向构造从该接口派生的特定类的函数的指针。

然后我有一个看起来大致如此的助手类:

base.hpp

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

实现(通过宏)创建此类的对象以注册自己:

impl1.cpp

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

此解决方案与C ++标准的兼容性如何?是否可以安全地假设类实例化ind impl1.cpp甚至会发生,因为主程序中的任何内容都不会在编译时显式地显式调用它?

提前感谢您的任何答案。

5 个答案:

答案 0 :(得分:3)

所以在看到Angew之前指出的位置的标准之后,我注意到标准34中的脚注[basic.start.init]§4

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).

这实际上解决了这里提到的问题。自注册类不是使用odr并修改工厂对象的状态,因此具有副作用的初始化。

所以根据这个脚注,我和Skizz所提到的自我注册实际上是安全的。

编辑:正如Matt McNabb所说,我不应该依赖脚注。因此,脚注所指的是规范的一部分:[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.

答案 1 :(得分:2)

不,通常不能保证可修改的impl1将被初始化。所有标准都表明,在调用同一个转换单元(相同的.cpp文件)中定义的第一个函数或首先使用该转换单元的变量之前,保证命名空间范围变量。

法律条文是C ++ 11 [basic.start.init]§4

  

实现定义是否在main的第一个语句之前完成具有静态存储持续时间的非局部变量的动态初始化。如果初始化被推迟到main的第一个语句之后的某个时间点,则它应该在与要初始化的变量相同的转换单元中定义的任何函数或变量的第一个odr-use(3.2)之前发生。

因此,如果您的impl1.cpp仅包含注册变量,则无法保证它们不会被初始化。但是,如果它包含任何在程序运行时(或从外部引用的变量)将被执行的函数,则保证在运行任何此类函数或引用变量之前初始化它们。

答案 2 :(得分:2)

我发现代码存在两个问题。首先,您正在使用动态分配,其次,您正在使用函数指针。这是我的解决方案: -

    #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;
    }

创建功能中没有错误检查,但我会把它作为读者的练习。

答案 3 :(得分:1)

从标准的角度来看,您可以确定该翻译单元是否包含在构建中。但是,从旧版本开始,Visual C ++静态库存在问题。为了安全起见,我在顶级控制中使用显式模块初始化,原始iostream实现所采用的技巧,其中头文件导致一个小的内部链接事物被初始化,这反过来导致模块初始化(如果尚未完成)。


哦,我有一个问题:任何人是否记得“Hoare envelope”模块初始化功能,或许可以指导我一些材料?我记得几年前的重新搜索,只是打了我自己早先的问题。令人沮丧。

答案 4 :(得分:1)

Here是解决此特定问题的此工厂模式的实现。

它确保工厂单例实现是在构造时调用注册器方法的实现。这意味着,如果使用工厂,则会发生注册。