R
Roshan
This is regarding the article titled "C++ & Double-Checked Locking" by
Scott Meyers and Andrei in DDJ July 2004 issue.
I think the reasoning in this article is fundamentally flawed due the
authors mixing up a couple of concepts (observable behavior & side
effects) that are defined separately in the standard.
Background: In the following statement...
class Singelton {
Singleton* pinstance;
public:
Singleton* instance() {
..
pinstance = new Singelton( ); // whats going on here ?
..
}
};
The assignment statement involves 3 steps. 1) allocate mem for object,
2) invoke constructor 3) assign address of object to pointer
The problem that the authors note is that within the rules of the
standard steps 2 and 3 can be executed in any order after step 1. This
is may be true as I cant find a good "sequence point" (SP) [defined in
1.9.7] that will disallow this. Yes there are 3 SPs here. One at start
of object's constructor, one at the end of the object's construtor and
one at the end of the assignment statement. But with just these 3 SPs I
am unable to make a solid case for strict ordering of 1,2 and 3. (the
authors fail to mention the first 2 SPs )
Now the authors claim that there is no way for the programmer to
express, in C or C++, the strict ordering that step 3 should follow step
2. I dont think this is the case. here is a trivial solution..
void Singleton::assign( Singleton * obj)
{
pinstance = obj;
}
Singleton* instance() {
..
assign ( new Singelton( ) ) ; // we introduce a SP before
assignment can occur
..
}
What I have done here is simply introduce a SP that will ensure the
correct ordering. i.e introduce a SP just after step 2 and right before
step 3.
At the heart of the matter is what the Scott writes to me in a pvt email
"As we tried to point out in the article, sequence points don't offer
very much help with this problem."
This is incorrect (I feel) as demonstrated by the solution above.
Now here is some standardese to back me up.....
1.9.6 // Definition of observable behavior
The observable behavior of the abstract machine is its sequence of reads
and writes to volatile data and
calls to library I/O functions.
1.9.7 // Definition of side effects and SP
Accessing an object designated by a volatile lvalue (3.10), modifying an
object, calling a library I/O
function, or calling a function that does any of those operations are
all side effects, which are changes in the
state of the execution environment. Evaluation of an expression might
produce side effects. 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.7)
1.9.12
A fullexpression is an expression that is not a subexpression of another
expression.
1.9.16
There is a sequence point at the completion of evaluation of each
fullexpression
1.9.17
When calling a function (whether or not the function is inline), there
is a sequence point after the evaluation
of all function arguments (if any) which takes place before execution of
any expressions or statements in
the function body. There is also a sequence point after the copying of a
returned value and before the execution
of any expressions outside the function11).
also 1.9.18 may be of interest but is not relevant for my argument.
Footnote:
All that said.. I am still not fully convinced that steps 2 and 3 can
execute in any order. Reasoning : Order of evaluation of assignment
operator is from right to left. So the "new expression" has to evaluated
completely first. But things get fuzzy as there is no "solid" SP here to
disallow the compiler from reordering. I am not sure what role the SPs
at the start and end of obect's contructor would play (if any) in
defining the strict ordering of the steps 2 and 3.
Scott Meyers and Andrei in DDJ July 2004 issue.
I think the reasoning in this article is fundamentally flawed due the
authors mixing up a couple of concepts (observable behavior & side
effects) that are defined separately in the standard.
Background: In the following statement...
class Singelton {
Singleton* pinstance;
public:
Singleton* instance() {
..
pinstance = new Singelton( ); // whats going on here ?
..
}
};
The assignment statement involves 3 steps. 1) allocate mem for object,
2) invoke constructor 3) assign address of object to pointer
The problem that the authors note is that within the rules of the
standard steps 2 and 3 can be executed in any order after step 1. This
is may be true as I cant find a good "sequence point" (SP) [defined in
1.9.7] that will disallow this. Yes there are 3 SPs here. One at start
of object's constructor, one at the end of the object's construtor and
one at the end of the assignment statement. But with just these 3 SPs I
am unable to make a solid case for strict ordering of 1,2 and 3. (the
authors fail to mention the first 2 SPs )
Now the authors claim that there is no way for the programmer to
express, in C or C++, the strict ordering that step 3 should follow step
2. I dont think this is the case. here is a trivial solution..
void Singleton::assign( Singleton * obj)
{
pinstance = obj;
}
Singleton* instance() {
..
assign ( new Singelton( ) ) ; // we introduce a SP before
assignment can occur
..
}
What I have done here is simply introduce a SP that will ensure the
correct ordering. i.e introduce a SP just after step 2 and right before
step 3.
At the heart of the matter is what the Scott writes to me in a pvt email
"As we tried to point out in the article, sequence points don't offer
very much help with this problem."
This is incorrect (I feel) as demonstrated by the solution above.
Now here is some standardese to back me up.....
1.9.6 // Definition of observable behavior
The observable behavior of the abstract machine is its sequence of reads
and writes to volatile data and
calls to library I/O functions.
1.9.7 // Definition of side effects and SP
Accessing an object designated by a volatile lvalue (3.10), modifying an
object, calling a library I/O
function, or calling a function that does any of those operations are
all side effects, which are changes in the
state of the execution environment. Evaluation of an expression might
produce side effects. 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.7)
1.9.12
A fullexpression is an expression that is not a subexpression of another
expression.
1.9.16
There is a sequence point at the completion of evaluation of each
fullexpression
1.9.17
When calling a function (whether or not the function is inline), there
is a sequence point after the evaluation
of all function arguments (if any) which takes place before execution of
any expressions or statements in
the function body. There is also a sequence point after the copying of a
returned value and before the execution
of any expressions outside the function11).
also 1.9.18 may be of interest but is not relevant for my argument.
Footnote:
All that said.. I am still not fully convinced that steps 2 and 3 can
execute in any order. Reasoning : Order of evaluation of assignment
operator is from right to left. So the "new expression" has to evaluated
completely first. But things get fuzzy as there is no "solid" SP here to
disallow the compiler from reordering. I am not sure what role the SPs
at the start and end of obect's contructor would play (if any) in
defining the strict ordering of the steps 2 and 3.