This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 116a. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.
2024-12-19
[Voted into WP at October, 2009 meeting.]
Consider the following code, which uses double-checked locking (DCL):
Widget* Widget::Instance() { if (pInstance == 0) { // 1: first check lock<mutex> hold(mutW); // 2: acquire lock if (pInstance == 0) { // 3: second check pInstance = new Widget(); // 4: create and assign } } // 5: release lock }
We want this to be fully correct when pInstance is an atomic pointer to Widget. To get that result, we have to disallow any assignment to pInstance until after the new object is fully constructed. In other words, we want this to be an invalid transformation of line 4:
pInstance = operator new(sizeof(Widget)); new (pInstance) Widget;
I don't think it would be surprising if this were disallowed. For example, if the constructor were to throw an exception, I think many people would expect the variable not to be modified. I think the question is whether it's sufficiently clearly disallowed.
This could be clarified by stating (somewhere appropriate — probably either in 7.6.2.8 [expr.new] paragraph 16 or paragraph 22) that the initialization of the allocated object is sequenced before the value computation of the new-expression. Then by 7.6.19 [expr.ass] paragraph 1 (“In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression.”), the initialization would have to be sequenced before the assignment.
This is probably not a problem for atomic<Widget*> because its operator= is a function, and function calls provide the necessary guarantees. But for the plain pointer assignment case, there's still a question about whether the sequencing of side effects is constrained as tightly as it should be. In fact, you don't even have to throw an exception from the constructor for there to be a question.
struct X { static X* p; X(); }; X* X::p = new X;
When the constructor for X is invoked by this new-expression, would it be valid for X::p to be non-null? If the answer is supposed to be “no,” then I think the Standard should express that intent more clearly.
Proposed resolution (March, 2008):
Change 7.6.2.8 [expr.new] paragraph 22 as indicated:
WhetherInitialization of the allocated object is sequenced before the value computation of the new-expression. It is unspecified whether the allocation function is called before evaluating the constructor arguments or after evaluating the constructor arguments but before entering the constructoris unspecified. It is also unspecified whether the arguments to a constructor are evaluated if the allocation function returns the null pointer or exits using an exception.
[Drafting note: The editor may wish to move paragraph 22 up to immediately follow paragraph 16 or 17. The paragraphs numbered 18-21 deal with the case where deallocation is done because initialization terminates with an exception, whereas paragraph 22 applies more to the initialization itself, described in paragraph 16.]
Notes from the September, 2008 meeting:
The proposed wording does not (but should) allow the call to the allocation function to occur in the middle of evaluating arguments for the constructor call.
Proposed resolution (July, 2009):
Change 7.6.2.8 [expr.new] paragraph 21 as follows:
Whether the allocation function is called before evaluating the constructor arguments or after evaluating the constructor arguments but before entering the constructor is unspecified.The invocation of the allocation function is indeterminately sequenced with respect to the evaluations of expressions in the new-initializer. Initialization of the allocated object is sequenced before the value computation of the new-expression. It isalsounspecified whetherthe arguments to a constructorexpressions in the new-initializer are evaluated if the allocation function returns the null pointer or exits using an exception.
[Drafting note: the editor may wish to consider moving this paragraph to follow paragraph 15 or 16. Paragraphs 17-19 deal with the case where deallocation is done because initialization terminates with an exception, whereas this paragraph applies more to the initialization itself (described in paragraph 15).]