Что происходит первым - стекирование или копирование возвращаемых значений

Является ли мьютекс, используемый в методе 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;
}