Каков порядок разрушения аргументов функции?
Если некоторая функция f
с параметрами p_1
,..., p_n
типов T_1
,..., T_n
соответственно вызывается с аргументами a_1
,..., a_n
и его тело выдает исключение, заканчивает или возвращает, в каком порядке уничтожаются аргументы и почему? Пожалуйста, предоставьте ссылку на стандарт, если это возможно.
EDIT: Я действительно хотел спросить о параметрах функции, но как T.C. и Коломбо сумел очистить мое замешательство, я оставляю этот вопрос в отношении аргументов и задал новый отдельный вопрос о параметрах. См. Комментарии по этому вопросу для различия.
Ответы
Ответ 1
Порядок, в котором оцениваются аргументы функции, не указан стандартом. Из стандарта С++ 11 (онлайн-проект):
5.2.2 Вызов функции
8 [Примечание. Оценки постфиксного выражения и выражений аргумента не имеют никакого значения относительно друг друга. Все побочные эффекты оценок выражения аргументов секвенированы до того, как функция (см. 1.9). -end note]
Следовательно, целиком решать задачу в каком порядке оценивать аргументы функции. Это, в свою очередь, подразумевает, что порядок построения аргументов также зависит от реализации.
Разумная реализация уничтожит объекты в обратном порядке их конструкции.
Ответ 2
Мне не удалось найти ответ в стандарте, но я смог проверить это на 3 самых популярных компиляторах, совместимых с С++. Ответ R Sahu в значительной степени объясняет, что это реализация определена.
§5.2.2/8. Оценки постфиксного выражения и аргументов всенепоследовательно относительно одного другой. Все побочные эффекты оценки аргументов секвенированы до ввода функции.
Компилятор Visual Studio С++ (Windows) и gcc (Debian)
Аргументы строятся в обратном порядке для их объявления и уничтожаются в обратном порядке (таким образом разрушаются в порядке деления):
2
1
-1
-2
Clang (FreeBSD)
Аргументы строятся в порядке их объявления и уничтожаются в обратном порядке:
1
2
-2
-1
Все компиляторы получили указание обработать исходный код как С++ 11, и я использовал следующий фрагмент, чтобы продемонстрировать ситуацию:
struct A
{
A(int) { std::cout << "1" << std::endl; }
~A() { std::cout << "-1" << std::endl; }
};
struct B
{
B(double) { std::cout << "2" << std::endl; }
~B() { std::cout << "-2" << std::endl; }
};
void f(A, B) { }
int main()
{
f(4, 5.);
}
Ответ 3
В §5.2.2 [4] N3337 довольно подробно сказано о том, что происходит (онлайн-проект):
Во время инициализации параметра реализация может избежать построения дополнительных временных рядов, объединив преобразования в соответствующем аргументе и/или построение временных инициализация параметра (см. 12.2). Время жизни параметра заканчивается, когда возвращается функция, в которой она определена.
Так, например, в
f(g(h()));
возвращаемое значение из вызова h()
является временным, которое будет уничтожено в конце полного выражения. Однако компилятору разрешено избегать этого временного и напрямую инициализировать с его значением параметр g()
. В этом случае возвращаемое значение будет уничтожено после возврата g()
(т.е. ПЕРЕД вызовом f()
).
Если я правильно понял то, что указано в стандарте, однако ему не разрешено возвращать значение из h()
, чтобы выжить до конца полного выражения, если только копия не сделана (параметр), и эта копия будет уничтожена один раз g()
возвращается.
Два сценария:
-
h
Возвращаемое значение используется для прямого инициализации параметра g
. Этот объект уничтожается, когда g
возвращается и перед вызовом f
.
-
h
Возвращаемое значение является временным. Копия выполняется для инициализации параметра g
, и он уничтожается, когда возвращается g
. Исходное временное название уничтожается в конце полного выражения.
Я не знаю, следуют ли правила реализации правил.