Re: Assign Reference to another Referance
On Sep 26, 12:02 am, cpisz <cp...@austin.rr.com> wrote:
class Singleton
{
public:
static Singleton & Instance()
{
if( !m_instance )
{
m_instance = new Singleton();
}
return *m_instance;
}
static void DoStuff()
{
int x = 1 + 1;
}
private:
static Singleton * m_instance;
};
Singleton * Singleton::m_instance = 0;
class Foo
{
public:
Foo(){}
~Foo()
{
Singleton::Instance().DoStuff();
}
};
int main()
{
static Foo foo;
return 0; // Undefined behavior after this line, when program
cleanup occurs!
}
static de-initialization fiasco.
Sorry for dropping in, maybe I have not the right grip on
the subject but I want to show what I understand about your
program, and take the chance to learn something new.
If I get it right, this is the sequence:
--- start of execution
- m_instance gets created
This isn't quite accurate. Static variables are "created"
before the start of execution. Or rather... variables aren't
created in C++: they are allocated and initialized, two separate
operations. Variables with static lifetime are allocated before
program execution (although there's no way to see this for a
local static). Variables with static lifetime and static
initialization (like m_instance, here) are initialized before
the start of execution.
And static variables with trivial destructors (which includes
all variables with basic types, and all pointers) have a
lifetime until the end of execution; they will never be
"destructed", and can be freely used anywhere, even in the
destructors of static objects. (Worries about order of
initialization and destruction are one reason to use C style
arrays and strings in certain cases.)
- foo gets created
The order of initialization is not defined. foo might get
created before m_instance or vica versa.
Sorry. The order of initialization is well defined here.
First, because m_instance has static initialization, it is
initialized before anything user written code (including
constructors of other static objects) is executed. Secondly
(but this typically won't apply in real code), because
m_instance is in the same translation unit as main, and all
static objects with namespace or class scope in a translation
unit are guaranteed to be initialized either before any
functions in the translation unit are called, or before main is
called. (The implementation gets to choose. In practice, the
first choice isn't implementable, so all implementations use the
second, and a lot of code counts on it.)
- main returns
Yes
- foo starts being destroyed
- a Singleton gets created
Maybe, maybe not. The order of deinitialization is also undefined.
There's only one object with a non-trivial destructor, so
there's no issue of order---how can you define an order between
only one object.
- x gets created
- x gets destroyed
This will happen only when foo gets destroyed, when that
happens isn't defined
It's defined to happen after the code returns from main.
- foo finishes its destruction
yes
- m_instance gets destroyed
m_instance doesn't have a destructor, so it never gets
destroyed.
Whether or not this occurs before foo is destroyed is
undefined. That is the problem and is the de-initialization
fiasco.
This could be a problem is m_instance were a smart pointer. I
think, however, that even the strongest proponents of smart
pointers would agree that in the case where you don't want to
destruct the pointed to object, you shouldn't use a smart
pointer with a non-trivial destructor. (One could imagine using
a smart pointer which e.g. prevented pointer arithmetic, but
still had a trivial destructor.)
--- end of execution
Have I got it right? How that relates to the
de-initialization fiasco?
Don't take me wrong, those are sincere questions.
-
The problem is there are two variables in static or global
space. The destruction of one relies on the other existing.
The reason that there is no problem is that one of these
variables doesn't have a destructor. This is the main reason
why we use a pointer and dynamic allocation, rather than just a
static variable.
[...]
The way to avoid this is to make sure that no global or static
depends on another in its destruction,
Independently of the example, this is a good principle. A
destructor exists to release resources, not to acquire them, and
a destructor (generally) should not do anything which can fail
(allocate memory, close a file, etc.). And in practice, I don't
think that I've ever seen a need for a destructor which uses a
singleton that wasn't used earlier. (That doesn't mean that the
case can't exist, however, and I do design my singletons so that
they work in this case.)
--
James Kanze