4

I have classes with side effects in their constructors, and objects of these classes are global objects that have static storage duration. During the initialization these objects register their classes in a special map, and it is important for these registrations to happen before this map is used for other purposes.

The classes and their global objects are defined in separate translation units, and according to the dynamic initialization of non-block variables rules, the initialization can be deferred until other functions or variables from these translation units are used, and that may mean that the initialization is deferred indefinitely.

I'm looking for a way to avoid deferred initialization of these global objects. Saying that I mean that the constructors of the objects shall be called either before main or right after its start, but all the changes shall be done inside translation units of these objects. In other words, whenever one more translation unit with global objects is added, no other translation unit shall be modified.

Update: here is an example:

struct Base {
};

extern void regFactoryMethod(std::function<std::shared_ptr<Base>()>);

struct Object : Base {
    struct Registrator {
        Registrator() {
            regFactoryMethod([](){ return std::make_shared<Object>(); });
        }
    };
    static Registrator registrator;
};

Object::Registrator Object::registrator;

The idea is that the Object::Registrator::Registrator() is automatically called. But it is not guaranteed that it would, as this call may be deferred:

It is implementation-defined whether the dynamic initialization of a non-block non-inline variable with static storage duration is sequenced before the first statement of main or is deferred. If it is deferred, it strongly happens before any non-initialization odr-use of any non-inline function or non-inline variable defined in the same translation unit as the variable to be initialized. It is implementation-defined in which threads and at which points in the program such deferred dynamic initialization occurs.

Dmitry Kuzminov
  • 6,180
  • 6
  • 18
  • 40
  • It sounds like you're dealing with the Static Initialization Order Fiasco. The idiomatic way to solve this (without converting your code to use some kind of global singleton manager) is to ditch all global variables and use static singleton "getter" functions. However, it sounds like your case might require a manager. To some extent, you already have one (with this global map thingy). So you could perhaps turn it into a class factory and have it build everything at the top of `main`. – paddy May 04 '23 at 03:38
  • 1
    @paddy, the problem is related to Static Initialization Order Fiasco, but is not exactly the same. In my case I don't care of the order, but I need to be sure that the initialization is done until some code is called. And I cannot use "getters", as the client code doesn't know all the objects that need to be initialized. The other way around: some objects themselves need to raise a flag: "INITIALIZE ME", and the client code needs to force the system to initialize everyone who raised the flag (but whom it is not even aware of). – Dmitry Kuzminov May 04 '23 at 03:48
  • Yeah that's why I mentioned a class factory might be the way to go. – paddy May 04 '23 at 03:49
  • 1
    How would class factory help? I'm adding a new class in a new translation unit without changing any other code. How would class factory know of the new class? – Dmitry Kuzminov May 04 '23 at 03:56
  • Because you "register" these classes already. As part of that, you can also register a lambda or some other interface that's responsible for actually constructing your instances. Then, your global registry can invoke them all to build your objects. Avoiding dynamic allocation (if desired) would be trickier: you'd either set up a static buffer with correct size/alignment and use placement-new (which can all be wrapped as a class template), or you could have your factory invoke the singleton "getter" pattern mentioned at the start. Either way, this can be contained in the translation unit. – paddy May 04 '23 at 04:07
  • To be honest, if none of this seems helpful, then your question could benefit from a simple code example that demonstrates exactly what you're trying to achieve. – paddy May 04 '23 at 04:08
  • That is what I'm trying to do: "register a lambda or some other interface that's responsible for actually constructing your instances". But there is a chicken or egg problem: to register a callback I need any code from the translation unit to be called first. – Dmitry Kuzminov May 04 '23 at 04:15
  • 1. I don't know. 2. Speculating in my ignorance: Would a, uh, proxy construction object work? In your translation unit, in an anonymous namespace define a constructor only class and a single static duration object of that class. The default constructor you write kicks the object you really want. Since the proxy object is not using dynamic initialization, it seems it would not be deferred and it's construction would pull forward the construction of the object of interest. – Avi Berger May 04 '23 at 04:15
  • 1
    @AviBerger, the construction of the proxy is not guaranteed, as it can be deferred indefinitely. – Dmitry Kuzminov May 04 '23 at 04:17
  • 1
    "It is implementation-defined whether the dynamic initialization of a non-block non-inline variable with static storage duration is sequenced before the first statement of main or is deferred. If it is deferred, it strongly happens before any non-initialization odr-use of any non-inline function or non-inline variable defined in the same translation unit as the variable to be initialized. It is implementation-defined in which threads and at which points in the program such deferred dynamic initialization occurs." – Dmitry Kuzminov May 04 '23 at 04:18
  • _"to register a callback I need any code from the translation unit to be called first"_ : Sure, but that the translation unit itself can call that code. You just have a class that registers your callback and define a static instance of it. During static initialization, everyone will register themselves via these objects. – paddy May 04 '23 at 04:23
  • OK, I see, default initialized won't cut it. The initialization has to be effectively constexpr to force it not to be deferred. And adding an init fcn to be called at the start of main would violate your auto handle additions of such translation units to the build. That seems to leave a custom pre-processing phase in the build to generate the code for the init routine. I don't think that will fly for you, either. Out of ideas. – Avi Berger May 04 '23 at 04:42
  • @AviBerger, `constexpr` and `constinit` move initialization to compile time, and that is the problem. – Dmitry Kuzminov May 04 '23 at 04:49
  • 1
    AFAIK, the guarantee you seek (constructors of all objects of static storage duration are executed, even if no function defined in the same translation unit as an object is called) is infeasible. Not ideal, but if it is infeasible to meet a requirement, the requirement must change (e.g. allow ONE other translation unit to be modified, to include a new type definition and define a static object of that type. The process may be simplified by automatically constructing a list of relevant types and generating that "other" translation unit and recompiling it during the build process.) – Peter May 04 '23 at 09:43
  • In other words, if the mountain can't come to Muhammed, Muhammed must go the mountain. – Peter May 04 '23 at 09:45
  • 1
    The solution you are looking for is known as Schwarz Counter, see https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_Counter – Maxim Egorushkin May 04 '23 at 16:23
  • @MaximEgorushkin, I'm not looking for Schwarz Counter. In my case there are no calls to the "Stream" object, the other way around: it is the Stream object that initiates communication to the rest of the system. – Dmitry Kuzminov May 04 '23 at 18:57
  • Your map/factory is the "Stream" object, it gets initialized by Schwarz Counter, like `std::cout`. Its users include factory header file in order to register/unregister with the factory in constructors/destructors of global objects, just like you'd use `std::cout` in constructors/destructors of global objects. Your common problem has long been solved. – Maxim Egorushkin May 04 '23 at 23:08
  • @MaximEgorushkin, if you are so self-confident, make an answer, and we would discuss your solution in details. But so far I would say that you are solving the problem opposite to my problem. – Dmitry Kuzminov May 05 '23 at 04:49
  • IMHO, your arguing is what prevents your understanding. My comments is the best I can do for you. – Maxim Egorushkin May 05 '23 at 12:29
  • The execution of constructors for static storage duration variables should be viewed as something that ensures that those variables are there when you need them, not a general mechanism for executing code without having to call it from `main`. – Brian Bi May 05 '23 at 15:12

1 Answers1

-1

As you noted in your question, it is implementation-defined whether dynamic initialization is deferred to the first non-initialization odr-use of a non-inline translation unit function/variable directly or indirectly through main.

There is also no other standard approach to execute code before main is entered.

So, I don't think you can get a guarantee that works independent of the implementation. The standard is also covering use cases such as dynamic loading of libraries at runtime, e.g. with dlopen. In these cases it cannot be possible to execute code contained in the library before main is entered.

You'll have to look for implementation guarantees or implementation specific features. For example with GCC/binutils on Linux, as long as you don't link with --as-needed, --gc-sections, etc., and you ignore dlopen, I think there won't be any deferred initialization.

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • My goal is not to execute code before main, but to execute it before any predicted moment. – Dmitry Kuzminov May 04 '23 at 17:00
  • 1
    @DmitryKuzminov What does "_predicted moment_" mean? – user17732522 May 04 '23 at 17:02
  • 1
    @DmitryKuzminov But it doesn't really matter either. It is not possible to transfer control from `main`'s execution to any code in another translation unit without referencing a declaration for an entity defined in that translation, or transitively through other translation units, assuming the translation unit hasn't been referenced in code executed before `main`. – user17732522 May 04 '23 at 17:05
  • So far the problem is that the code may never be executed. – Dmitry Kuzminov May 04 '23 at 17:10
  • @DmitryKuzminov In theory yes, but do you actually see that behavior on some implementation? If so, with what options did you link the program? It is _implementation-defined_, not _unspecified_. – user17732522 May 04 '23 at 17:11