8

cppreference says about std::atexit :

The functions may be called concurrently with the destruction of the objects with static storage duration and with each other, maintaining the guarantee that if registration of A was sequenced-before the registration of B, then the call to B is sequenced-before the call to A, same applies to the sequencing between static object constructors and calls to atexit

I understand that passage to mean that, if std::atexit is called during static initialization, the registered function will be called during the destruction of static objects just before the destruction of the static object which was last initialized when the std::atexit that registered the function was called. I also interpret "may be called concurrently" to mean the calls can occur between static object destructions as opposed to the multi-threaded interpretation of the word.

What I'm wondering is whether a object is considered as initialized (in the context of this ordering) when it's initialization begins or when it completes. I wrote a short test to test this :

#include <cstdlib>
#include <iostream>

struct foo
{
    foo() 
    {
        std::cout << "ctor\n";
        std::atexit([]() { std::cout << "atexit\n"; });
    }
    ~foo()
    {
        std::cout << "dtor\n";
    }
};

foo my_foo;

int main()
{
    return 0;
}

The output I get is (http://cpp.sh/3bllu) :

ctor
dtor
atexit

This leads me to believe that my_foo is not considered to be initialized in this context until it's construction finishes. In other words, the function is considered to have been registered before my_foo was initialized so the registered function executes after my_foo's destruction.

I can't seem to find anything that would guarantee this behavior and I'm not even entirely sure my initial interpretation of the cited passage is correct. Is the behavior I've described something that I can rely on or is it implementation defined or even undefined behavior?

François Andrieux
  • 24,129
  • 6
  • 46
  • 72
  • 1
    Object initialization is considered complete at construction completion in general. See how destructors get handled during exception handling when exceptions get thrown inside a constructor as another example of that. – Frank May 16 '18 at 19:56
  • Even is it was defined, implementations are not exactly behaving well with regard to destruction order of static variables. You even have extremely broken implementations like VisualC++, which dare to hard kill all threads first, and then invoke static variable destructors in an undefined (and often corrupted) state. – Ext3h May 16 '18 at 20:00
  • Friends don't let friends use global variables. Seriously; get rid of all global variables (and that includes singletons) and you'll live a happier life in the long run. – Jesper Juhl May 16 '18 at 20:01
  • [\[decl.init\]/21](https://timsong-cpp.github.io/cppwp/dcl.init#21) leads me to believe you can guarantee that the constructor has to finish before initialization is complete. – NathanOliver May 16 '18 at 20:01
  • 2
    @JesperJuhl oh really? Good-bye, `std::cout`... – SergeyA May 16 '18 at 20:02
  • @SergeyA nitpick. I think you very well know what I *meant* by that comment. Nothing is black/white. – Jesper Juhl May 16 '18 at 20:03
  • 2
    @JesperJuhl, no, I do not. You literally said 'all global variables, including singletons'. I can't imagine a complex application which doesn't use a logger which is a global variable or a singleton, for example. – SergeyA May 16 '18 at 20:04
  • @SergeyA Fine. Let me be more specific. Don't create any globals/singletons of your own - they lead to tight coupling and hard to debug creation/destruction/ordering problems/bugs. Better/more clear? – Jesper Juhl May 16 '18 at 20:07
  • @JesperJuhl, ok, back to logger example. How do you log in your program? – SergeyA May 16 '18 at 20:08
  • @SergeyA by passing a reference to a log object (created in `main`) to all functions that need to write log messages. – Jesper Juhl May 16 '18 at 20:10
  • 3
    How is this conversation (argument) constructive or useful to the original question? – ricco19 May 16 '18 at 20:12
  • @JesperJuhl you really do? You pass around the poor logger all the way around? Ok, let me tell you, I have never seen a real application like that. – SergeyA May 16 '18 at 20:16
  • @SergeyA yes. Really. Although in some applications I use a sepperate log process and write to it via shared memory. – Jesper Juhl May 16 '18 at 20:18
  • @JesperJuhl, same difference. Where shared memory address does come from? Passed around from the main? – SergeyA May 16 '18 at 20:20
  • @SergeyA yep. You guessed it. – Jesper Juhl May 16 '18 at 20:32
  • 1
    @JesperJuhl Out of curiosity, do you also refuse to use `static const` members? They are essentially the same thing as globals in the context of static initialization. In any case eliminating all globals is not always as easy as it sounds, specially when considering legacy code and dependencies. And sometimes, on occasion, the gymnastics of avoiding a global are just not worth the man hours or code complexity. – François Andrieux May 16 '18 at 21:30

1 Answers1

7

The call to the destructor will happen before the call to the function passed to atexit. From [basic.start.term], p5:

If a call to std::atexit strongly happens before the completion of the initialization of an object with static storage duration, the call to the destructor for the object is sequenced before the call to the function passed to std::atexit.

1201ProgramAlarm
  • 30,320
  • 7
  • 40
  • 49