Yes I did, but again he doesn't answer the question of how a global object can be initialized. He only illustrates how a class's static members can be initialized... which I already understand.
I understand that cout's static members can be guaranteed to be initialized with the nifty counter.
What I don't understand is how cout itself (ie: the actual object... the nonstatic members) can be initialized.
Does cout simply not have any nonstatic members?
xerzi wrote:
Well cout wouldn't need to worry about that as it's being linked from an external library (stdlib).
I don't believe this is correct. Are you sure about this or are you just guessing?
ios_base::Init::Init()
{
if (__gnu_cxx::__exchange_and_add_dispatch(&_S_refcount, 1) == 0)
{
// Standard streams default to synced with "C" operations.
_S_synced_with_stdio = true;
new (&buf_cout_sync) stdio_sync_filebuf<char>(stdout);
...
new (&cout) ostream(&buf_cout_sync);
That's a good question - the example JLBorges linked to uses a reference to the object and it is initialized to refer to just a block of data, but with the cout implementations shown here I am pretty confused.
Pete Becker's article says it uses placement new, which is a shitty solution IMO because it requires the constructor to be called twice... which seems extremely flakey to me, especially if the class is polymorphic.
What about doing something like this?
1 2 3 4 5 6 7 8 9
// header
extern uint8_t foo_buffer[ sizeof( Foo ) * 2 ];
static Foo& foo = *reinterpret_cast<Foo*>(foo_buffer + align_x);
static FooInitializer initializer;
// initializer uses placement new on foo_buffer, rather than on 'foo' directly
This way the ctor/dtor only get called once. The downside is that we're technically dereferencing 'foo_buffer' before its initialized in order to set the 'foo' reference.
Notes:
- add 'align_x' to the offset in the buffer so we can make sure memory is aligned properly
- allocate 2x space for the Foo object (to ensure alignment padding does not overflow)
EDIT: Regarding the downside...
That dereferencing is OK because it's valid allocated memory! The memory itself hasn't been initialized yet... but that doesn't matter because placement new will overwrite it anyway.
Pete Becker's article says it uses placement new, which is a shitty solution IMO because it requires the constructor to be called twice... which seems extremely flakey to me, especially if the class is polymorphic.
Apparently you didn't finish the article. You might want to check out the code immediately before the Conclusion header.
> ...
> Notes:
> - add 'align_x' to the offset in the buffer so we can make sure memory is aligned properly
> - allocate 2x space for the Foo object (to ensure alignment padding does not overflow)
1. Defining the type-punned buffer with external linkage (and declaring it as such in the header) can lead to undefined behaviour - it grossly violates the 'strict aliasing' rules of C++.
// implementation
namespace { alignas(Foo) char foo_buffer[ sizeof(Foo) ] ; } // internal linkage
Foo& foo = *reinterpret_cast<Foo*>(foo_buffer) ; // external linkage
// type-punned; this deliberately breaks strict aliasing rules
// we know it is safe here as 1. foo_buffer is not programmatically visible outside a.cc
// and 2. within the implementation, it is never accessed as an array of char