为什么一个全局对象的构造函数中的工厂成员函数可能会挂起?

huangapple go评论70阅读模式
英文:

Why might a factory member function hang in the constructor for a global object?

问题

以下是要翻译的内容:

要先声明,我没有一个完整的最小功能示例来定义工厂,因为我无法访问我正在使用的动态库的定义。

我现在正在寻找阅读建议,除非我的问题显然是一个通用问题,不需要知道函数定义来解释。

假设我们有某种工厂模式的变体,允许以下内容编译并按照随附的DLL文档的说明正常运行:

int main(){
   std::cout << "开始" << std::endl;

   Base* ptr = Base::Create();
   ptr->ConcreteMemberDoSomething();

   std::cout << "结束" << std::endl;
}

输出:

开始
/-- 具体成员...输出 --/
结束

有了这个想法,为什么这段代码可以编译,但运行时(一致地)导致程序无限挂起呢?

class Init{
public:
   Base* ptr;
   Init(){
      std::cout << "构造函数开始" << std::endl;

      ptr = Base::Create();

      std::cout << "构造函数结束" << std::endl;
   };
   ~Init(){
      std::cout << "析构函数" << std::endl;
   };
}

Init obj;

int main(){
   std::cout << "开始" << std::endl;

   obj.ptr->ConcreteMemberDoSomething();

   std::cout << "结束" << std::endl;
}

输出:

构造函数开始

我预计我需要对我的 main() 进行更多调试,但我不明白为什么 Init 构造函数会冻结。我担心它与初始化顺序有关,因为我一直在研究静态初始化顺序问题,但由于我无法访问动态库中编译的任何定义,我不知道可以尝试什么来修复它。

英文:

To preface, I do not have a complete minimum functional example here to define the factory because I don't have access to the definitions of the dynamic library I'm using.

What I'm looking for right now is suggestions for reading, unless my issue is clearly a general one and doesn't require knowledge of the function definitions to explain.

Suppose we have some variation of a factory pattern which allows the following to compile and operate as intended, per the documentation that came with the DLL:

int main(){
   std::cout << "Start" << std::endl;

   Base* ptr = Base::Create();
   ptr->ConcreteMemberDoSomething();

   std::cout << "End" << std::endl;
}

With output:

Start
/-- Concrete Member... Output --/
End

With that in mind, why might this compile but (consistently) cause the program to hang indefinitely when run?:

class Init{
public:
   Base* ptr;
   Init(){
      std::cout << "Ctor start" << std::endl;

      ptr = Base::Create();

      std::cout << "Ctor end" << std::endl;
   };
   ~Init(){
      std::cout << "Dtor" << std::endl;
   };
}

Init obj;

int main(){
   std::cout << "Start" << std::endl;

   obj.ptr->ConcreteMemberDoSomething();

   std::cout << "End" << std::endl;
}

With output:

Ctor start

I expect that I have more debugging to do with my main(), but I don't understand why the Init constructor freezes. I'm worried it's something to do with the initialization order, since I've been reading about the static initialization order problem, but I don't know what I could try to fix it since I don't have access to any of the definitions compiled in the dynamic library.

答案1

得分: 1

这可能是静态初始化顺序混乱的一个潜在情况。尽管可能存在其他问题,但此猜测仅基于提供的代码片段。一般来说,创建具有动态初始化的非函数本地静态对象被视为不良的编程实践,如果确实不可避免(例如 std::cout),则必须考虑经过精心设计的特定解决方法

解决此问题的一种方法被称为Scott Mayer的Singleton。用户代码不得存储单例对象的指针或引用,并始终使用 singleton_t::instance()。有些人建议将Mayer的单例包装在单态类型中:

struct mono{
  single* const operator ->() const{
     static single ret{/*TODO:init stuff here*/};
     return std::addressof(ret);
  };
};
//...
{
  //在某些代码范围内:
  mono x;
  use(x->y);
};

在OP的上下文中,假设存在一个createdestroy对应项,我们可以使用unique_ptr

struct mono{
  auto const operator ->() const{
     static std::unique_ptr
               <Base,
               decltype([](Base *const ptr)
                       {Base::Destroy(ptr);})>
          ret {Base::Create()};
     return ret;
  };
};

如果存在Destroy并且不打算删除对象,那么可以使用原始指针而不是智能指针。

英文:

This is a probable case of static initialization order fiasco. Although other sorts of problems are probable, this speculation is based solely on the provided snippet code. Generally speaking, creating none-function-local static objects with dynamic initialization is considered bad programming practice and if it is indeed inevitable (e.g std::cout), carefully designed ad-hoc workarounds must be considered.

One way to get around this problem is known as Scott Mayer's Singleton. User code must not store any pointers or references to the singleton object and always use singleton_t::instance(). Some recommend wrapping the Mayer's singleton in a monostate type:

struct mono{
&#160;&#160;&#160; single* const operator -&gt;() const{
&#160;&#160;&#160;&#160;&#160;&#160;&#160; static single ret{/*TODO:init stuff here*/};
&#160;&#160;&#160;&#160;&#160;&#160;&#160; return std::addressof(ret);
&#160;&#160;&#160; };
};
//...
{
&#160;&#160;&#160; //In Some code scope:
&#160;&#160;&#160; mono x;
&#160;&#160;&#160; use(x-&gt;y);
};

In the context of OP, assuming the existence of a destroy counterpart for create we can use a unique_ptr:

struct mono{
&#160;&#160;&#160; auto const operator -&gt;() const{
&#160;&#160;&#160;&#160;&#160;&#160;&#160; static std::unique_ptr
                    &lt;Base,
                    decltype([](Base *const ptr)
                            {Base::Destroy(ptr);})&gt;
               ret {Base::Create();};
&#160;&#160;&#160;&#160;&#160;&#160;&#160; return ret;
&#160;&#160;&#160; };
};

If the such Destroy exists and the object is not supposed to be deleted at all, then instead of a smart pointer, a raw pointer can be used.

huangapple
  • 本文由 发表于 2023年3月31日 03:55:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/75892449.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定