Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

static initialisation fiasco

46 views
Skip to first unread message

Frederick Gotham

unread,
Aug 8, 2020, 5:26:58 AM8/8/20
to

Looking at this question in the FAQ:

https://isocpp.org/wiki/faq/ctors#static-init-order

So let's say you start off with:

// File x.cpp
#include "Fred.hpp"
Fred x;

// File y.cpp
#include "Barney.hpp"
Barney y;

// File Barney.cpp
#include "Barney.hpp"
#include "Fred.hpp"
Barney::Barney()
{
// ...
x.goBowling();
// ...
}

// File main.cpp
#include <iostream>
#include "Fred.hpp"
#include "Barney.hpp"

int main(void)
{
cout << x.Hello() << endl;
cout << y.Hello() << endl;
}

Well the first thing I'd do is change the first two source files like this:

// File x.cpp
alignas(Fred) char storage_for_x[sizeof(Fred)];
Fred &x = *static_cast<Fred*>(static_cast<void*>(&storage_for_x));

// File y.cpp
alignas(Barney) char storage_for_y[sizeof(Barney)];
Barney &y = *static_cast<Barney*>(static_cast<void*>(&storage_for_y));

And then I'd create two function, PreMain and PostMain:

void PreMain(void)
{
::new(&x) typename std::remove_reference<decltype(x)>::type;
::new(&y) typename std::remove_reference<decltype(y)>::type;
}

template<class T>
void PostMain_Destruct(T *const p)
{
p->~T();
}

void PostMain(void)
{
PostMain_Destruct(&y);
PostMain_Destruct(&x);
}

And then I'd write 'main' like this:

int main(void)
{
PreMain();

std::atexit(PostMain);

cout << x.Hello() << endl;
cout << y.Hello() << endl;
}

Bo Persson

unread,
Aug 8, 2020, 7:11:32 AM8/8/20
to
> [snip]


Another option would be to just not have

x.goBowling();

in the constructor. What is it doing there in the first place?!

This just reminds me of the old "Doctor, doctor, it hurst when I do this!".



Bo Persson

Mr Flibble

unread,
Aug 8, 2020, 8:49:02 AM8/8/20
to
A correct solution might be:

struct Dweeb
{
// ...
};

typedef std::vector<std::shared_ptr<Dweeb>> Dweebs;
typedef Dweebs BowlingParty;

struct Bowling
{
Bowling(BowlingParty& Party);
};

int main()
{
Dweebs dweebs{ "Fred", "Barney" };
Bowling(dweebs);
}

Or to put it another way: PRESENT A REALISTIC FUCKING USE-CASE THAT MAKES ACTUAL SENSE FOR WHATEVER IS ACTUALLY RUBBING YOUR FUCKING RHUBARB.

/Flibble

--
"Snakes didn't evolve, instead talking snakes with legs changed into snakes." - Rick C. Hodgin

“You won’t burn in hell. But be nice anyway.” – Ricky Gervais

“I see Atheists are fighting and killing each other again, over who doesn’t believe in any God the most. Oh, no..wait.. that never happens.” – Ricky Gervais

"Suppose it's all true, and you walk up to the pearly gates, and are confronted by God," Byrne asked on his show The Meaning of Life. "What will Stephen Fry say to him, her, or it?"
"I'd say, bone cancer in children? What's that about?" Fry replied.
"How dare you? How dare you create a world to which there is such misery that is not our fault. It's not right, it's utterly, utterly evil."
"Why should I respect a capricious, mean-minded, stupid God who creates a world that is so full of injustice and pain. That's what I would say."

Mr Flibble

unread,
Aug 8, 2020, 8:54:02 AM8/8/20
to
Of course my correct solution has glaring errors but Usenet posts are read only and I don't actually give a fuck.

I leave finding the glaring errors as an exercise for the reader.

Sam

unread,
Aug 8, 2020, 1:42:44 PM8/8/20
to
Frederick Gotham writes:

> Well the first thing I'd do is change the first two source files like this:
>
> // File x.cpp
> alignas(Fred) char storage_for_x[sizeof(Fred)];
> Fred &x = *static_cast<Fred*>(static_cast<void*>(&storage_for_x));
>
> // File y.cpp
> alignas(Barney) char storage_for_y[sizeof(Barney)];
> Barney &y = *static_cast<Barney*>(static_cast<void*>(&storage_for_y));

There are far simpler ways to solve the static initialization order fiasco
that does not require jumping through your own asshole like that, or any
similar ugliness.

Fred &getFred()
{
static Fred real_fred;

return real_fred;
}

Calling getFred() from any translation unit makes the right thing will
happen. Ditto for Barney.

Juha Nieminen

unread,
Aug 8, 2020, 7:33:53 PM8/8/20
to
Sam <s...@email-scan.com> wrote:
> Fred &getFred()
> {
> static Fred real_fred;
>
> return real_fred;
> }
>
> Calling getFred() from any translation unit makes the right thing will
> happen. Ditto for Barney.

Just take into account that that will create a conditional with
thread-safe locking, which is used every time getFred() is called.

(I don't know what kind of solution compiles will usually use for
this. Perhaps some kind of atomic variable. But either way, it won't
be zero-cost, and it most probably won't be even as "cheap" as a
regular if-conditional.)

If maximum efficiency is not a concern with this, then by all means.
If you need to squeeze every single clock cycle out of it, then it's
not the best possible solution.

thomas.h...@gmail.com

unread,
Aug 9, 2020, 5:36:28 AM8/9/20
to
in some cases this can cause the static de-initialisation fiasco

Juha Nieminen

unread,
Aug 10, 2020, 2:06:36 AM8/10/20
to
thomas.h...@gmail.com wrote:
>> Fred &getFred()
>> {
>> static Fred real_fred;
>>
>> return real_fred;
>> }
>>
>> Calling getFred() from any translation unit makes the right thing will
>> happen. Ditto for Barney.
>
>
> in some cases this can cause the static de-initialisation fiasco

I don't think it's a good idea for a destructor to rely on outside resources
which might or might not exist by the time the destructor is called.

If class A depends on an object of class B, then it may be better if that
object is a member of A, not an outside object.

Jorgen Grahn

unread,
Aug 10, 2020, 7:07:44 AM8/10/20
to
On Mon, 2020-08-10, Juha Nieminen wrote:
> thomas.h...@gmail.com wrote:
>>> Fred &getFred()
>>> {
>>> static Fred real_fred;
>>>
>>> return real_fred;
>>> }
>>>
>>> Calling getFred() from any translation unit makes the right thing will
>>> happen. Ditto for Barney.
>>
>>
>> in some cases this can cause the static de-initialisation fiasco

I haven't paid attention to the thread, and may be misunderstanding,
but ...

> I don't think it's a good idea for a destructor to rely on outside resources
> which might or might not exist by the time the destructor is called.

Wouldn't that apply just as much during the object's normal lifetime?

struct A {
B& b;
// ...
};

We can do this only if we have control over the relative lifetimes of
the A and the B, and then we can have control during A's destruction
too.

To me, this is often a standard part of the design. My main() often
looks something like

A a;
B b(a);
C c(a);
D d(b, c);
...

Although I concede that:

- Destructors shouldn't do too much "global stuff" in either case.
- This breaks horribly if you start having global objects, std::shared_ptr
to them, threads and shared libraries. But (see above) then you
don't have control over lifetimes.

> If class A depends on an object of class B, then it may be better if that
> object is a member of A, not an outside object.

/Jorgen

--
// Jorgen Grahn <grahn@ Oo o. . .
\X/ snipabacken.se> O o .
0 new messages