Is the mutex used in method GetValues()
released before or after copy constructing the dummy
instance?
The standard is not particularly explicit on the matter as far as I can tell, but here's what I've managed to get together:
Automatic storage duration objects are destroyed as per 6.7 when the block they are declared in exits. - 3.7.2
On exit from a scope, destructors (12.4) are called for all automatic storage duration (3.7.2) (named objects and temporaries) that are declared in that scope, in reverse order of their declaration. - 6.6
A return statement with an expression of non-void type can be used only in functions returning a value; the value of the expression is returned to the caller of the function. The expression is implicitly converted to the return type of the function in which it appears. A return statement can involve the construction and copy of a temporary object (12.2). - 6.6.3
Even when the creation of the temporary object is avoided (12.6), all the semantic restrictions must be respected as if the temporary object was created. - 12.2
This seems to generally confirm what James said: on return m_protVal;
, a temporary is created, and then destructors of all objects that must be destroyed are called in reverse order of their declaration (in this case, only the destructor of lock
is called). However, I am not entirely sure how to interpret the following:
Temporary objects are destroyed as the last step in evaluating the full-expression (1.9) that (lexically) contains the point where they were created. - 12.2
A full-expression is defined as "as expression that is not a subexpression of another expression". I don't know what part of return m_protVal
is the full expression: geordi says that this is a
decl'ion-seq → decl'ion → func-def → func-body → compound-stmt → stmt-seq → stmt → jump-stmt
while i
by itself is a
decl'ion-seq → decl'ion → func-def → func-body → compound-stmt → stmt-seq → stmt → jump-stmt → … → id-expr → unqual-id → ident
This makes it unclear whether the temporary may be copied and destroyed prior to the rest of the destructors being called: I would say it may not, as return m_protVal;
causes the end of the block to be reached, but I can't find anything in the standard that would confirm this.
(On the other hand, I don't see any case where such behaviour would cause any breakage; nobody should have a pointer to the temporary so it being destroyed first is not an issue, and if the temporary had a pointer to a local variable, the problems come in when the temporary is destroyed later -- not that writing code like that is a good idea in any case.)
As of Visual C++, using MSVC 9.0, the following code
int add(int x, int y)
{
int z;
z = x + y;
return z;
}
int _tmain(int argc, _TCHAR* argv[])
{
add(10, 20);
return 0;
}
results in assembly
int add(int x, int y)
{
013613A0 push ebp //save the frame pointer
013613A1 mov ebp,esp
013613A3 sub esp,0CCh
013613A9 push ebx
013613AA push esi
013613AB push edi
013613AC lea edi,[ebp-0CCh]
013613B2 mov ecx,33h
013613B7 mov eax,0CCCCCCCCh
013613BC rep stos dword ptr es:[edi]
int z;
z = x + y;
013613BE mov eax,dword ptr [x] //load x
013613C1 add eax,dword ptr [y] //add y to x
013613C4 mov dword ptr [z],eax //store the result to z
return z;
013613C7 mov eax,dword ptr [z] //store the return value in eax
}
013613CA pop edi //unwind the stack
013613CB pop esi
013613CC pop ebx
013613CD mov esp,ebp
013613CF pop ebp
013613D0 ret
int _tmain(int argc, _TCHAR* argv[])
{
013613E0 push ebp
013613E1 mov ebp,esp
013613E3 sub esp,0C0h
013613E9 push ebx
013613EA push esi
013613EB push edi
013613EC lea edi,[ebp-0C0h]
013613F2 mov ecx,30h
013613F7 mov eax,0CCCCCCCCh
013613FC rep stos dword ptr es:[edi]
add(10, 20);
013613FE push 14h
01361400 push 0Ah
01361402 call add (136109Bh)
01361407 add esp,8
return 0;
0136140A xor eax,eax //store 0 to eax, the return value holder
}
0136140C pop edi //unwind the stack
0136140D pop esi
0136140E pop ebx
This makes me say that return value is stored first and then the stack unwinding happens!
Here is a small complete test programm (in addition to James Kanzes good explanation), which will show if the unlock is done before or after stack unwinding:
#include <iostream>
class PseudoLockGuard
{
public:
enum LockState { IsStillLocked, IsUnlocked};
PseudoLockGuard(LockState& value) : m_value(value) {};
~PseudoLockGuard() { m_value = IsUnlocked; };
private:
LockState& m_value;
};
PseudoLockGuard::LockState Test()
{
PseudoLockGuard::LockState indicator = PseudoLockGuard::IsStillLocked;
PseudoLockGuard lock(indicator);
return indicator;// Will return IsStillLocked or IsUnlocked?
}
int main(int , char** )
{
PseudoLockGuard::LockState result = Test();
std::cout << (result == PseudoLockGuard::IsStillLocked
? "Return Value before Unlock"
: "Return Value after Unlock");
// Outputs "Return Value before Unlock"
return 0;
}
Yes:-). Formally, there are two “copies” when returning a value: one to some special place used to actually return the value, and the second after the return, to wherever the value must be finally placed. Either or both can be optimized out, however. The destruction of local variables occurs after the first, but before the second. (NRVO and RVO may lead to the first being optimized out, but they don't affect your code, since you're not returning a local variable.)