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

When passing a class by-value, does the caller or callee call the 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)在其 returns 之前对其所有按值参数调用析构函数,然后调用者释放内存(通过将其从堆栈中弹出)。
  2. 被调用者什么都不做,调用者(即bar)在foo(p)调用结束时的序列点之前的所有临时对象上调用析构函数。

调用方销毁它。参见 https://en.cppreference.com/w/cpp/language/lifetime。引用:

All temporary objects are destroyed as the last step in evaluating the full-expression that (lexically) contains the point where they were created, and if multiple temporary objects were created, they are destroyed in the order opposite to the order of creation.

也将此作为一般规则 - 一个,谁创造,谁破坏。通常以相反的顺序。

每当对象的生命周期结束时都会调用析构函数,其中包括

end of scope, for objects with automatic storage duration and for temporaries whose life was extended by binding to a reference

所以复制对象的所有者bar将调用复制对象的dtorCppreference

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

foo 中的对象 P x 开始运行的地方创建函数堆栈的那一刻,对象将是 "created"。因此,最终将通过离开作用域删除对象,在您的情况下,通过离开函数。

因此,在引入新对象的函数中有一个局部作用域,然后将这个作用域留在同一个函数中,理论上没有区别。

编译器能够"see"你的对象是如何被使用的,特别是被修改的并且可以通过内联函数也跳过创建一个"temporary"对象只要代码行为"as if"写的。

标准在 [expr.call]/4 中回答了这个问题,详细程度惊人:

... The initialization and destruction of each parameter occurs within the context of the calling function. [ Example: The access of the constructor, conversion functions or destructor is checked at the point of call in the calling function. If a constructor or destructor for a function parameter throws an exception, the search for a handler starts in the scope of the calling function; in particular, if the function called has a function-try-block (Clause 18) with a handler that could handle the exception, this handler is not considered. —end example ]

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