推迟C++静态对象构造 - Linux上的GCC

9
假设我有一个名为 MyClass 的 C++ 类。
假设我无法访问 MyClass 的源代码...它包含在一个库中,我只提供了此库和 MyClass 的头文件。
想象一下,这个类本身需要环境预配置...例如...在调用类的构造函数之前,我需要进行一些设置。该类通常应按以下方式使用:
void func() {
   doGlobalSetup();
   MyClass myInstance(1,2,3);
   myInstance.doSomething();
   ...
}

现在我面临的情况是需要创建一个类的全局实例,例如:
MyClass myInstance(1,2,3);

int main(int argc, char *argv[]) {
   doGlobalSetup();
   myInstance.doSomething();
}

问题在于,在这个故事中,MyClass 的实例是在调用 doGlobalSetup() 之前创建的。它在调用 main() 之前实例化。我想要做的是将 myInstance() 的创建推迟到后面,或者在类的实例化之前以某种方式运行 doGlobalSetup()
这只是实际故事的简化版本...因此假设:
  1. 我不能改变 MyClass 的内部结构。
  2. 必须存在一个名为myInstance的类型为MyClass的实例变量(我不能将逻辑更改为MyClass *pMyInstance)。
感谢阅读。

MyClass 可以被复制吗?相关的,我真的会考虑是否要创建一个全局实例... 但是,如果它是可复制的,那么我认为有一个简单的解决方案。 - James Adkison
1
请停止使用void main,ISO C++ 仅允许使用 int main()int main(int, char**) - Danh
@Danh - 抱歉Danh...它本来是伪代码,而不是一个真正的应用程序。但是我已按要求更改了文本。 - Kolban
7个回答

5

因为您限制了问题,使得不能使用 new,所以您可以像往常一样创建对象并将其复制到全局实例中。例如:

MyClass createMyClass()
{
    doGlobalSetup();
    return MyClass(1, 2, 3);
}

MyClass myInstance = createMyClass();

int main()
{
    myInstance.doSomething();

    return 0;
}

@Arkadiy 是的,它假设 MyClass 是可复制的,并且问题没有列出另外的限制条件。我也问过 OP 关于这个问题,但是没有得到答案。 - James Adkison

3

这是否符合您的需求?

namespace
{
    int doStaticGlobalSetup()
    {
        doGlobalSetup();
        return 0;
    }
}
MyClass myInstance(doStaticGlobalSetup() + 1,2,3);

int main() {
   myInstance.doSomething();
   return 0;
}

3
如果你必须要将任何一个构造函数的调用推迟到全局初始化完成之后,并且想确保不会发生静态顺序初始化失败,有一种方法:将myInstance作为对未初始化内存块的引用,并在全局初始化之后使用放置new创建对象。
#include <iostream>
#include <type_traits>

struct foo
{
    foo() { std::cout << "created\n"; }
    void meow() { std::cout << "used\n"; }
    ~foo() { std::cout << "destroyed\n"; }
};
void doGlobalSetup() { std::cout << "Global setup\n"; }


//Actual implementation
namespace {
    typename std::aligned_storage<sizeof(foo), alignof(foo)>::type bar;
}
foo& instance = reinterpret_cast<foo&>(bar);

//Allows automatic creation and destruction
struct initializer
{
    initializer()
    {
        if (!initialized)
            new (&instance) foo();
        initialized = true;
    }
    ~initializer()
    {
        if(initialized)
            instance.~foo();
        initialized = false;
    }
    private:
        static bool initialized;
};
bool initializer::initialized = false;

int main()
{
    doGlobalSetup();
    initializer _;
    instance.meow();
}

我不确定它是否回答了这个问题。它回答了吗? - YSC
2
@YSC 为什么不这样做:放置新对象听起来是个好主意,特别是如果对象不可复制。 - James Adkison
我明白了。一开始放置新的答案并不明显,但它确实有用。 - YSC
我认为这是最好的答案。我只会做一点小改动:将create()destroy()作为代理类的构造函数和析构函数,并在main()开始时创建该类的实例(并在调用doGlobalSetup()之后)。这样,我仍然可以自动管理实例的生命周期。 - Cássio Renan

3

在函数内使用静态变量。

MyClass &myInstance() {
   doGlobalSetup();
   static MyClass myInstance(1,2,3);
   return myInstance;
}

void func() {
   myInstance().doSomething();
}

1
您可能已经得到了想要的答案。但是为了覆盖整个领域:如果出于某种原因,您希望确保代码中的其他位置不会在全局变量之前--并且在全局设置已完成之前--意外构造MyClass,则需要使用链接来解决此问题。
如果您在Linux上,可以LD_PRELOAD一个仅包含MyClass构造函数符号的共享对象。在其中,您相应地声明设置函数,并让动态链接器为您完成工作。然后,在构造函数内部,调用设置函数,然后执行dlsym("...", RTLD_NEXT)以获取指向原始构造函数的指针,并将其调用,传递您获得的参数。当然,您维护和检查静态标志以确定是否已执行设置。
再次强调,这对您来说可能过于复杂,但我发布它只是为了万一有人需要(并能够使用)这种解决方案。
附注:这就是当您依赖全局状态时会发生什么! :)

哦,不,这正是我要找的。在我的实际难题中,我无法控制实际调用doGlobalSetup()的过程。想象一下,我正在一个框架中工作,在这个框架中,我必须编写一个具有已知名称的C++函数。在我的C++函数中,我有全局状态类实例。当我将我的C++函数与框架链接时,全局类对象在框架准备好之前和我的链接用户代码函数被控制之前就被实例化了。 - Kolban

0
首先,请记住,如果您创建全局实例,则像doGlobalSetup这样的库初始化函数存在明显的非零机会,该库将无法正常工作
否则,使用逗号运算符创建初始化程序非常容易:
bool do_my_setup = (doGlobalSetup(), true);
MyClass myInstance(1,2,3);

0
在GCC编译器环境中,有一个名为constructor的函数属性能力。这使我们能够标记函数定义,使其能够在调用main之前自动调用,最重要的是在调用任何类构造函数之前自动调用。
回顾原始问题定义...如果doGlobalSetup()函数被修改为:
void doGlobalSetup() { ... }

__attribute__((constructor)) void doGlobalSetup() { ... }

那么它的调用将在main函数被调用之前以及任何静态类实例构造函数被调用之前发生。由于其工作已经隐式完成,因此对该函数的显式调用也将从main()中删除。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接