John Leidegren
John Leidegren

Reputation: 61007

Crazy talk (paranoid about initialization)

I learned long ago that the only reliable way for a static member of be initialized for sure is to do in a function. Now, what I'm about to do is to start returning static data by non-const reference and I need someone to stop me.

function int& dataSlot()
{
    static int dataMember = 0;
    return dataMember;
}

To my knowledge this is the only way to ensure that the static member is initlized to zero. However, it creates obscure code like this:

dataSlot() = 7; // perfectly normal?

The other way is to put the definition in a translation unit and keep the stuff out of the header file. I have nothing against that per se but I have no idea what the standard says regard when and under what circumstances that is safe.

The absolute last thing I wanna end up doing is accidently accessing uninitialized data and losing control of my program.

Upvotes: 1

Views: 182

Answers (5)

BЈовић
BЈовић

Reputation: 64223

It is called Meyers singletor, and it is almost perfectly safe.

You have to take care that the object is created when the function dataSlot() is called, but it is going to be destroyed when the program exists (somewhere when global variables are destructed), therefore you have to take special care. Using this function in destructors is specially dangerous and might cause random crashes.

Upvotes: 3

Jan Hudec
Jan Hudec

Reputation: 76296

I learned long ago that the only reliable way for a static member of be initialized for sure is to do in a function.

No, it isn't. The standard guarantees that:

  1. All objects with static storage (both block and file or class-static scope) with trivial constructors are initialized before any code runs. Any code of the program at all.
  2. All objects with file/global/class-static scope and non-trivial constructos are than initialized before the main function is called. It is guaranteed that if objects A and B are defined in the same translation unit and A is defined before B, than A is initialized before B. However order of construction of objects defined in different translation units is unspecified and will often differ between compilations.
  3. Any block-static objects are initialized when their declaration is reached for the first time. Since C++03 standard does not have any support for threads, this is NOT thread-safe!
  4. All objects with static storage (both block and file/global/class-static scoped) are destroyed in the reverse order of their constructors completing after the main() function exits or the application terminates using exit() system call.

Neither of the methods is usable and reliable in all cases!

Now, what I'm about to do is to start returning static data by non-const reference and I need someone to stop me.

Nobody is going to stop you. It's legal and perfectly reasonable thing to do. But make sure you don't fall in the threads trap.

E.g. any reasonable unit-test library for C++ automatically registers all test cases. It does it by having something like:

std::vector<TestCase *> &testCaseList() {
    static std::vector<TestCase *> test_cases;
    return test_cases;
}

TestCase::TestCase() {
    ...
    testCaseList().push_back(this);
}

Because that's the one of only two ways to do it. The other is:

TestCase *firstTest = NULL;

class TestCase {
    ...
    TestCase *nextTest;
}

TestCase::TestCase() {
    ...
    nextTest = firstTest;
    firstTest = this;
}

this time using the fact that firstTest has trivial constructor and therefore will be initialized before any of the TestCases that have non-trivial one.

dataSlot() = 7; // perfectly normal?

Yes. But if you really want, you can do either:

  1. The old C thing of

    #define dataSlot _dataSlot()
    

    in a way the errno "variable" is usually defined,

  2. Or you can wrap it in a struct like

    class dataSlot {
        Type &getSlot() {
            static Type slot;
            return slot;
        }
        operator const Type &() { return getSlot(); }
        operator=(Type &newValue) { getSlot() = newValue; }
    };
    

    (the disadvantage here is that compiler won't look for Type's method if you try to invoke them on dataSlot directly; that's why it needs the operator=)

Upvotes: 2

Tom Tanner
Tom Tanner

Reputation: 9354

You could make yourself 2 functions, dataslot() and set_dataslot() which are wrappers round the actual dataslot, a bit like this:

int &_dataslot() { static int val = 0; return val; }
int dataslot() { return _dataslot(); }
void set_dataslot(int n) { _dataslot() = n; }

You probably wouldn't want to inline that lot in a header, but I've found some C++ implementations do rather badly if you try that sort of thing anyway.

Upvotes: 0

Steve Jessop
Steve Jessop

Reputation: 279265

Returning a non-const reference in itself is fairly harmless, for example it's what vector::at() does, or vector::iterator::operator*.

If you don't like the syntax dataSlot() = 7;, you could define:

void setglobal(int i) {
    dataSlot() = i;
}
int getglobal() {
    return dataSlot();
}

Or you could define:

int *dataSlot() {
    static int dataMember = 0;
    return &dataMember;
}

*dataSlot() = 7; // better than dataSlot() = 7?
std::cout << *dataSlot(); // worse than std::cout << dataSlot()?

If you want someone to stop you, they need more information in order to propose an alternative to your use of mutable global state!

Upvotes: 4

Marcelo Cantos
Marcelo Cantos

Reputation: 185862

(With the usual cautions against indiscriminate use of globals...) Just declare the variable at global scope. It is guaranteed to be zero-initialized before any code runs.

You have to be more cunning when it comes to types with non-trivial constructors, but ints will work fine as globals.

Upvotes: 6

Related Questions