Re: Guarantee of side-effect free assignment
* 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).
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.
And the acknowledgments for the article include quite a few well-known
people as reviewers, presumably catching a fundamental mistake like
that, if it was a mistake: "Pre-publication drafts of this article were
reviewed by Doug Lea, Kevlin Henney, Doug Schmidt, Chuck Allison, Petru
Marginean, Hendrik Schober, David Brownell, Arch Robison, Bruce Leasure,
and James Kanze. Their comments, insights, and explanations greatly
improved the presentation of the paper and led us to our current
understanding of DCLP, multithreading, instruction ordering, and
compiler optimizations. After publication, we incorporated comments by
Fedor Pikus, Al Stevens, Herb Sutter, and John Hicken."
So, I'm at a loss, since I now find your argument, that the assignment
to result pointer has to happen after the constructor body's execution
if the constructor can throw, quite convincing: I started writing a
rebuttal but had to delete and write this instead. ;-)
Hm.
Cheers,
- Alf
Notes:
[1] <url: http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf>.
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
---
[ 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 ]