Что происходит первым - стекирование или копирование возвращаемых значений
Является ли мьютекс, используемый в методе GetValues()
выпущенным до или после, копируя экземпляр dummy
?
class Protect
{};
class Test
{
public:
Protect GetValues() const;
private:
Protect m_protVal;
Mutex m_mutex;
};
Protect Test::GetValues() const
{
CLockGuard lock(m_mutex);
return m_protVal;
}
int main(int argc, char** argv)
{
Test myTestInstance;
Protect dummy = myTestInstance.GetValues();
}
Предположим, что CLockGuard
и Mutex
являются стандартными классами, снабженными boost lib.
Ответы
Ответ 1
Да:-). Формально существует два "экземпляра" при возврате
value: один для некоторого специального места, используемого для фактического возврата значения, и
второй после возвращения, где бы ни было значение должно быть окончательно
размещены. Однако оба или оба могут быть оптимизированы. Разрушение
локальных переменных происходит после первого, но до второго. (NRVO
и RVO может привести к тому, что первое будет оптимизировано, но они не влияют
ваш код, так как вы не возвращаете локальную переменную.)
Ответ 2
Как и в Visual С++, используя MSVC 9.0, следующий код
int add(int x, int y)
{
int z;
z = x + y;
return z;
}
int _tmain(int argc, _TCHAR* argv[])
{
add(10, 20);
return 0;
}
приводит к сборке
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
Это заставляет меня сказать, что возвращаемое значение сохраняется сначала, а затем происходит разворот стека!
Ответ 3
Стандарт не особенно ясен по этому вопросу, насколько я могу судить, но вот что мне удалось собрать:
Объекты продолжительности автоматического хранения уничтожаются в соответствии с 6.7, когда блок объявляются в выходы. - 3.7.2
При выходе из области действия деструкторы (12.4) вызываются для всех автоматических хранилищ длительность (3.7.2) (именованные объекты и временные), объявленные в этой области, в обратном порядке их декларации. - 6.6
Оператор return с выражением не-void-типа может использоваться только в функциях возврат значения; значение выражения возвращается вызывающей стороне функция. Выражение неявно преобразуется в возвращаемый тип функции в котором он появляется. Оператор возврата может включать в себя создание и копию временный объект (12.2). - 6.6.3
Даже если избежать создания временного объекта (12.6), все семантические ограничения должны соблюдаться, как если бы был создан временный объект. - 12.2
Это, как правило, подтверждает то, что сказал Джеймс: на return m_protVal;
создается временное создание, а затем деструкторы всех объектов, которые должны быть уничтожены, вызываются в обратном порядке их объявления (в этом случае только деструктор lock
). Однако я не совсем уверен, как интерпретировать следующее:
Временные объекты уничтожаются как последний шаг при оценке полного выражения (1.9) что (лексически) содержит точку, в которой они были созданы. - 12.2
Полное выражение определяется как "как выражение, которое не является подвыражением другого выражения". Я не знаю, какая часть return m_protVal
является полным выражением: geordi говорит, что это
decl'ion-seq → decl'ion → func-def → func-body → composite-stmt → stmt-seq → stmt → jump-stmt
в то время как i
сам по себе является
decl'ion-seq → decl'ion → func-def → func-body → состав-stmt → stmt-seq → stmt → jump-stmt →... → id-expr → unqual-id → ident
Это не дает понять, может ли временное копирование и уничтожение до того, как будут вызваны остальные вызывающие деструкторы: я бы сказал, что это может быть не так, поскольку return m_protVal;
приводит к достижению конца блока, но я могу " t найти что-нибудь в стандарте, которое подтвердит это.
(С другой стороны, я не вижу случая, когда такое поведение приведет к поломке: никто не должен иметь указатель на временное, поэтому сначала его уничтожать не является проблемой, и если у временного указателя есть локальная переменная, проблемы возникают, когда временное уничтожается позже, а не то, что такой код является хорошей идеей в любом случае.)
Ответ 4
Вот небольшая полная тестовая программа (в дополнение к замечательному объяснению Джеймса Канзеса), в котором будет показано, выполняется ли разблокировка до или после: p >
#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;
}