假设我有以下(经过精简的)代码:
class P { P(); P(const P&); ~P(); }
void foo(P x) {
...
}
void bar() {
P p{};
foo(p); // compiler uses P::(const P&) to construct the value for x
...
// compiler calls P::~P() on p
}
编译器必须创建p
的副本才能调用foo
,因此 caller 会在调用之前调用副本构造函数。我的问题是,谁负责破坏这个创建的对象?似乎有两个有效的选择:
foo
)在其返回值之前使用其所有按值参数调用析构函数,然后调用方释放内存(通过将其弹出堆栈)。bar
)在foo(p)
调用结束时在序列点之前调用所有临时变量的析构函数。答案 0 :(得分:12)
标准在[expr.call] / 4中回答了这个问题,并做了很多令人惊讶的阐述:
...每个参数的初始化和销毁发生在 调用函数。 [示例:检查构造函数,转换函数或析构函数的访问 在调用函数中的调用点。如果函数参数的构造函数或析构函数抛出 例外,对处理程序的搜索始于调用函数的范围;特别是如果功能 被调用有一个 function-try-block (第18条),带有一个可以处理异常的处理程序,该处理程序不是 考虑过的。 -最终示例]
换句话说,析构函数由调用函数调用。
答案 1 :(得分:1)
呼叫者将其销毁。参见https://en.cppreference.com/w/cpp/language/lifetime。报价:
所有临时对象都被销毁,这是评估对象的最后一步 完全表达(按词法)包含它们所在的点 创建的,如果创建了多个临时对象,则它们是 破坏顺序与创建顺序相反。
这也应作为一般规则-一个创造,摧毁的人。通常以相反的顺序。
答案 2 :(得分:0)
只要对象的生存期结束,就会调用析构函数,其中包括
范围的结尾,用于具有自动存储期限的对象以及 通过绑定到引用而延长了寿命的临时人员
因此,作为复制对象所有者的bar
将在复制对象上调用dtor
。
Cppreference
答案 3 :(得分:0)
呼叫者和被呼叫者的想法对我来说似乎是错误的。您应该在这里想到scopes
。
在P x
中的对象foo
处于活动状态的情况下创建函数的堆栈时,将“创建”该对象。这样,最后将通过离开范围将对象删除,在您的情况下,将通过离开函数来删除对象。
因此,在函数内部具有局部范围(引入新对象),然后将此范围留在同一函数中,在理论上没有区别。
编译器能够“查看”对象的使用方式,特别是对其进行了修改,并且只要代码的行为“像”一样,通过内联函数还可以跳过“临时”对象的创建。