传递类按值时,调用方或被调用方是否调用析构函数?

时间:2019-06-13 15:44:41

标签: c++ language-lawyer destructor

假设我有以下(经过精简的)代码:

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 会在调用之前调用副本构造函数。我的问题是,谁负责破坏这个创建的对象?似乎有两个有效的选择:

  1. 被调用方(即foo)在其返回值之前使用其所有按值参数调用析构函数,然后调用方释放内存(通过将其弹出堆栈)。
  2. 被叫方不执行任何操作,并且调用方(即bar)在foo(p)调用结束时在序列点之前调用所有临时变量的析构函数。

4 个答案:

答案 0 :(得分:12)

标准在[expr.call] / 4中回答了这个问题,并做了很多令人惊讶的阐述:

  

...每个参数的初始化和销毁​​发生在   调用函数。 [示例:检查构造函数,转换函数或析构函数的访问   在调用函数中的调用点。如果函数参数的构造函数或析构函数抛出   例外,对处理程序的搜索始于调用函数的范围;特别是如果功能   被调用有一个 function-try-block (第18条),带有一个可以处理异常的处理程序,该处理程序不是   考虑过的。 -最终示例]

换句话说,析构函数由调用函数调用。

答案 1 :(得分:1)

呼叫者将其销毁。参见https://en.cppreference.com/w/cpp/language/lifetime。报价:

  

所有临时对象都被销毁,这是评估对象的最后一步   完全表达(按词法)包含它们所在的点   创建的,如果创建了多个临时对象,则它们是   破坏顺序与创建顺序相反。

这也应作为一般规则-一个创造,摧毁的人。通常以相反的顺序。

答案 2 :(得分:0)

只要对象的生存期结束,就会调用析构函数,其中包括

  

范围的结尾,用于具有自动存储期限的对象以及   通过绑定到引用而延长了寿命的临时人员

因此,作为复制对象所有者的bar将在复制对象上调用dtorCppreference

答案 3 :(得分:0)

呼叫者和被呼叫者的想法对我来说似乎是错误的。您应该在这里想到scopes

P x中的对象foo处于活动状态的情况下创建函数的堆栈时,将“创建”该对象。这样,最后将通过离开范围将对象删除,在您的情况下,将通过离开函数来删除对象。

因此,在函数内部具有局部范围(引入新对象),然后将此范围留在同一函数中,在理论上没有区别。

编译器能够“查看”对象的使用方式,特别是对其进行了修改,并且只要代码的行为“像”一样,通过内联函数还可以跳过“临时”对象的创建。