Re: Guarantee of side-effect free assignment
On Oct 8, 9:26 pm, "Alf P. Steinbach" <al...@start.no> wrote:
* Greg Herlihy:
The program reaches plenty of sequence points between the evaluation
of the new-expression and the completion of the assignment operation.
As the Standard itself points out, a new expression makes a function
call (actually, several of them). And a C++ program arrives at a
sequence point whenever a called function is entered - and arrives at
another upon exit. So, based on the Standard's definition of a
sequence point:
"At certain specified points in the execution sequence called sequence
points, all side effects of previous evaluations shall be complete and
no side effects of subsequent evaluations shall have taken place."
intro.execution/7]
we can conclude that - at the point when the new-expression makes its
function call - the assignment to p must a) either be over or b) must
have not yet begun. Well, since the value assigned to "p" is dependent
on the value returned by the new-expression, the only possibility is
that the assignment to "p" must fall in the evaluations "not-yet-
started" category. Therefore we are assured that when the function
call is made, p will still have its last-assigned value (the null
pointer constant).
Here the dependency "on the value returned by the new-expression" is, as
I understand it, really a dependency on the possible
not-returning-a-value by a throwing constructor, and otherwise the
dependency is only on the allocator function call (permitting the
reordering, your option (a) for that function call).
I'm not sure what you mean by "not-returning-a-value by a throwing
constructor", constructors don't return values. I'll assume you mean
simply not returning.
The crux of the problem seems to be whether there really is any
dependency between returning the value and calling the constructor.
James Kanze claims (elsewhere in this thread) that calling the
constructor is a side effect by the standard's definition and that
there is no such dependency. His argument is not entirely
unconvincing.
Assuming the above argument holds, then, the Meyers/Alexandrescu
assumption[1] also holds, that the reordering shown below is permitted
when the compiler can prove that the constructor doesn't throw.
Singleton* Singleton::instance()
{
if (pInstance == 0)
{
Lock lock;
if (pInstance == 0)
{
pInstance = // Step 3
operator new(sizeof(Singleton)); // Step 1
new (pInstance) Singleton; // Step 2
}
}
return pInstance;
}
<quote>
there are conditions under which this transformation is legitimate.
Perhaps the simplest such condition is when a compiler can prove that
the Singleton constructor cannot throw (e.g., via post-inlining flow
analysis), but that is not the only condition. Some constructors that
throw can also have their instructions reordered such that this
problem arises.
</quote>
I'm now quoting in full what the article actually says because earlier
in the thread I erred by paraphrasing the above quote, saying that it
stated that the rewrite can "only" occur when the constructor is
provably non-throwing, which is less permissive than the actual text.
So that seems to leave an interesting possibility of safe double-checked
locking pattern using a constructor that can't be proven by the compiler
to not throw -- which is easy enough to arrange, e.g. by dependency on
dynamic data, e.g. checking a global initialized from a main() argument.
S::S()
{
// Some initialization here, then:
if( strcmp( ::dynData, ::someUuid ) == 0 ){ throw "never"; } }
}
Yet, the Meyers/Alexandrescu article states categorically that
<quote>
DCLP will work only if steps 1 and 2 are completed before step 3 is
performed, but there is no way to express this constraint in C or
C++.
</quote>
"there is no way to express this constraint" -- i.e. not even the
S::S() constructor shown above is safe from willy-nilly assignment of
result pointer before the constructor body's execution has finished.
There is no way to express this constraint _explicitly_. You can
attempt to force the compiler to avoid the optimisation -- but as they
say later in the article
"In essence, you've just fired the opening salvo in a war of
optimization. Your
compiler wants to optimize. You don't want it to, at least not here.
But this is
not a battle you want to get into. Your foe is wiley and
sophisticated, imbued
with strategems dreamed up over decades by people who do nothing but
think
about this kind of thing all day long, day after day, year after year.
Unless you
write optimizing compilers yourself, they are way ahead of you."
Even if you're right that the value must not be written until after
the constructor call, the compiler could write the value and restore
the original value in the event of a throw. Even if you inspect the
value from the constructor, written in a different translation unit,
the compiler could inline the constructor at link time and give the
correct value. Eventually you might be able to go far enough to thwart
the compiler; but how far do you have to go to be sure? (And that's
ignoring hardware reorderings.)
Yechezkel Mett
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]