views:

184

answers:

4

A comment to http://stackoverflow.com/questions/945232/whats-wrong-with-this-fix-for-double-checked-locking says:

The issue is that the variable may be assigned before the constructor is run (or completes), not before the object is allocated.

Let us consider code:

A *a;

void Test()
{
    a = new A;
}

To allow for more formal analysis, let us split the a = new A into several operations:

void *mem = malloc(sizeof(A)); // Allocation
new(mem) A; // Constructor
a = reinterpret_cast<A *>(mem); // Assignment

Is the comment quoted above true, and if it is, in what sense? Can Constructor be executed after the Assignment? If it can, what can be done against it when guaranteed order is needed because of MT safety?

+1  A: 

a is a global object with static storage duration so it's going to be initialised in some pre-allocated storage sometime before the body of main get executed. Assuming that a call to Test isn't the result of some static object construction weirdness, a will be full constructed by the time Test is called.

a = new A;

This slightly unusual assignment isn't going to be (only) a standard copy assignment operation as you are assigning a pointer to A to a, not an object or reference. Whether it actually compiles and what exactly it calls depends on whether A has an assignment operator that takes a pointer to A, or something implicitly convertable from a pointer to A or whether A has an non-explicit constructor that takes a pointer to A (or a pointer to a base class of A).

Post edit, your code does something rather different!

Conceptually, it does something more like this:

A *tmpa;
void *mem = ::operator new( sizeof(A) ); // ( or possibly A::operator new )

try
{
    tmpa = new (mem) A; // placement new = default constructor call
}
catch (...)
{
    ::operator delete( mem );
    throw;
}

a = tmpa; // pointer assignment won't throw.

The peril with writing something out like this is that your implicitly adding a lot of sequence points that just aren't there in the original, and in addition the compiler is allowed to generate code that doesn't look like this so long as it behaves 'as if' it were written by this as far as the executing program could determine. This 'as if' rule only applies to the executing thread as the (current) language says nothing about interaction with other threads works.

For this you need to use the specific behaviour guarantees (if any) proided by your implementation.

Charles Bailey
Your answer was based on my code containing typo (missing *). Sorry for that, code is fixed now.
Suma
A: 

Yes, the constructor can be called after the assignment, although the example you gave is not internally consistent (As noted by a comment on it).

You can throw in some locks for peace of mind, but it's easy to get that wrong, too.

see

"C++ and the Perils of Double-Checked Locking"

by Scott Meyers and Andrei Alexandrescu

http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf

Alex Brown
+1  A: 

The issue isn't so much when code executes, but more to do with write-ordering.

Let's suppose:

A()
{
   member = 7;
}

Then later:

singleton = new A()

This results in code that does an allocation, a write to memory (member), and then a write to another memory location (singleton). Some CPU's can re-order writes such that the write to member will not be visible until after the write to singleton - in essence, code running on other CPU's in the system could have a view of memory where singleton is written to, but member is not.

Michael
+1  A: 

I think following should work:

void Test()
{
    A *temp = new A;
    MemoryWriteBarrier(); // use whatever memory barrier your platform offers
    a = temp;
}
Suma