我很确定,应该在相应函数的出口处调用函数参数的析构函数。考虑C ++ 11标准的5.2.2p4:
[...]参数的生存期在定义它的函数返回时结束。[...]
但是,让我们尝试以下代码:
#include <iostream>
using namespace std;
struct Logger {
Logger(int) { cout << "Construct " << this << '\n'; }
Logger(const Logger&) { cout << "Copy construct " << this << '\n'; }
~Logger() { cout << "Destruct " << this << '\n'; }
};
int f(Logger)
{
cout << "Inside f\n";
return 0;
}
int main()
{
f(f(f(10)));
}
用gcc或clang编译后,输出将如下所示:
Construct 0x7fffa42d97ff
Inside f
Construct 0x7fffa42d97fe
Inside f
Construct 0x7fffa42d97fd
Inside f
Destruct 0x7fffa42d97fd
Destruct 0x7fffa42d97fe
Destruct 0x7fffa42d97ff
如我们所见,所有三个参数仅在最后一个函数调用完成后才被销毁。这是正确的行为吗?
要详细说明,现在可以在注释中找到:
给定int f(Logger);
,当您编写时:
f(10);
这(从概念上)构造一个临时Logger
对象,从该临时对象构造函数参数,调用该函数,销毁该函数参数,最后销毁该临时对象。
当你写:
f(f(10));
这(从概念上)构造一个临时Logger
对象,从该临时对象构造函数参数,调用该函数,销毁该函数参数,Logger
使用第一个函数调用的结果构造一个新的临时对象,从该临时对象构造函数参数,调用函数,销毁函数参数,最后销毁两个临时对象。
我会尽量避免写出来f(f(f(10)));
。
现在,可以省略这两个临时对象:
当满足某些条件时,即使该对象的复制/移动构造函数和/或析构函数具有副作用,也允许实现忽略类对象的复制/移动构造。在这种情况下,实现将忽略的复制/移动操作的源和目标视为引用同一对象的两种不同方式,并且该对象的销毁发生在两个对象本来应该以较晚的时间发生。没有优化就销毁。在以下情况下允许复制/移动操作的这种省略,称为复制删除(可以合并以消除多个副本):
...
当尚未绑定到引用(12.2)的临时类对象将被复制/移动到具有相同cv-unqualtype类型的类对象时,可以通过将临时对象直接构造到目标中来省略复制/移动操作省略的副本/移动的
...
由于函数参数和临时对象具有相同的类型,因此允许编译器将它们视为相同的对象。临时对象将在最后阶段被销毁,因此该参数的寿命不会发挥作用。
但是,当不执行复制省略时,例如,因为您未配置编译器,或者因为首先没有要复制的副本(请参见下文),那么当您说函数参数应该删除时,确实必须销毁它们。在所有符合C ++ 11的实现中,在第二个函数调用开始之前,您必须先看到“ Destruct(...)”。
使用花括号可以构造一个没有临时参数的参数:您可以将调用重新处理为
f({f({f({10})})});
这里,每个参数都是列表初始化的,在这种情况下,它不涉及临时对象,也没有要删除的副本。在所有符合C ++ 11的实现中,无论是否有任何命令行选项,这都必须在函数f
返回之前立即销毁函数参数,而实际上,编译器不执行此操作是一个需要解决的问题他们不符合C ++ 11。f
-felide-constructors
但是,它并不是那么简单:CWG 1880年的内容如下:
WG决定不指定在调用之后还是在调用所属的完整表达式的结尾销毁参数对象。
这将完全允许编译器执行以下操作:在完整表达式结束后,最后一个f
返回之后,可以销毁参数。C ++ 11的确切文字文本不是当前编译器实现的。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句