0

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.

CoilKid
  • 335
  • 2
  • 3
  • 12
  • 1
    they both do the same thing effectively. From what you have shown it is not possible to know why one works while the other doesnt – 463035818_is_not_an_ai Mar 30 '23 at 19:57
  • get the debug version of the library and attach a debugger then you will know where it hangs – 463035818_is_not_an_ai Mar 30 '23 at 19:59
  • @463035818_is_not_a_number That's why I'm confused. What information would I need/need to provide to narrow down the set of possible problems? – CoilKid Mar 30 '23 at 19:59
  • it is `Base::Create()` that doesnt return so the issue seems to be there. – 463035818_is_not_an_ai Mar 30 '23 at 20:00
  • I bet you have a bug in the code not provided. But it's hard to tell without a [mcve], and I wasn't able to divine the suspected bug. – Eljay Mar 30 '23 at 20:00
  • btw I doubt that it is actually `Base::Create()`. I suppose in reality its a factory that takes some parameters, and i hope it returns not a raw pointer. That might already help. Its information you should have available – 463035818_is_not_an_ai Mar 30 '23 at 20:01
  • @Eljay I've only recently started learning about factory patterns. It's immediately obvious that the DLL developers used a factory pattern, but their implementation is very confusing to me since they appear to have mixed-and-matched portions from different "standard" patterns. It's not clear to me from the headers alone how they compiled it without major dependency issues. – CoilKid Mar 30 '23 at 20:03
  • @463035818_is_not_a_number They give examples that are essentially `myClass* ptr = myClass::Create(&parameters)`. I would expect that `myClass` is the client class, but `myClass` has pure virtual members and both dependent classes that I've found headers for are set up as subclasses of `myClass`. Would you say the issue I've described would probably become apparent if I focused on understanding how they've set up their factory, if that's possible without the class definitions? – CoilKid Mar 30 '23 at 20:13
  • from what I see and read, I would suggest you focus on understanding polymorphism first. Only then you can understand how a factory works and what it is good for – 463035818_is_not_an_ai Mar 30 '23 at 20:15
  • 1
    Testing for static initialization order fiasco is not hard: put the definition of `obj` inside `main` instead of global, to check if that is the case. If positive, then come back for the solution. – Red.Wave Mar 30 '23 at 20:32
  • @Red.Wave That did indeed compile, with the desired output order: `Ctor start`, `Ctor end`, `Start`, `/-- Concrete Member... Output --/`, `End`,`Dtor`. – CoilKid Mar 30 '23 at 20:50
  • 1. Do you need a global/static object? If not, then don't make it global/static. 2. Is there a `Base::Destroy` method? Or you have to `delete` the returned object? Or you are supposed to totally leave the destruction to the dynamic library? What is the memory management schema? – Red.Wave Mar 30 '23 at 21:48
  • @Red.Wave Unfortunately, I do need a global/static object. I need the scope of the concrete object to be as broad as I can get it. There is not a `Base::Destroy` method, no. I suspect that `Base::create` `new`'s a derived class object, and `~Base` directs through to the `delete`. Memory management is entirely up to the dynamic library here. – CoilKid Mar 30 '23 at 21:54
  • To be clear I already extensively explored creating a global object, or an explicit memory allocation, and copying the data from the `Base::create` to that. However, there is nontrivial data involved somewhere (byte copies don't work) and since the factory interface is an abstract class, I only have a pointer to work with. – CoilKid Mar 30 '23 at 22:04
  • Now my typos are gone. See if the answer is fit. – Red.Wave Mar 30 '23 at 22:19
  • There will be something specific to your situation that has caused the problem you are seeing - and you haven't provided relevant information, so nobody can help you. There are many possibilities for why a program might hang during construction of an object (code with undefined behaviour [notionally, an infinite number of ways behaviour may be undefined], compiler bugs, operating system refusing to allocate a resource and your program waits indefinitely for it to be allocated, etc etc) so asking for a set of possible reasons rather than offering specifics on your situation is pointless. – Peter Mar 31 '23 at 00:10
  • @Peter There are probably subtleties I need to consider before finalizing my implementation, but I believe Red.Wave's answer has solved my immediate problem. For my reading, I expect I'll need to go learn about singletons now. Thanks! – CoilKid Mar 31 '23 at 03:08

1 Answers1

1

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{
    single* const operator ->() const{
        static single ret{/*TODO:init stuff here*/};
        return std::addressof(ret);
    };
};
//...
{
    //In Some code scope:
    mono x;
    use(x->y);
};

In the context of OP, assuming the existence of a destroy counterpart for create we can use a 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;
    };
};

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.

Red.Wave
  • 2,790
  • 11
  • 17
  • While I now have other issues to deal with, I believe your answer has solved the immediate problem I had asked about. Much appreciated! Edit: to be clear, my other issues were expected and (I believe) are unrelated to the issue I had asked about. – CoilKid Mar 31 '23 at 03:02
  • Start new threads then – Red.Wave Mar 31 '23 at 09:49