按值传递 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 在调用之前调用复制构造函数。我的问题是,谁负责销毁这个创建的对象?似乎有两个有效的选择:
- 被调用者(即
foo
)在其 returns 之前对其所有按值参数调用析构函数,然后调用者释放内存(通过将其从堆栈中弹出)。
- 被调用者什么都不做,调用者(即
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
将调用复制对象的dtor
。
Cppreference
呼叫者和被呼叫者的想法对我来说是错误的。这里你应该想到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 ]
换句话说,析构函数由调用函数调用。
假设我有以下(精简)代码:
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
)在其 returns 之前对其所有按值参数调用析构函数,然后调用者释放内存(通过将其从堆栈中弹出)。 - 被调用者什么都不做,调用者(即
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
将调用复制对象的dtor
。
Cppreference
呼叫者和被呼叫者的想法对我来说是错误的。这里你应该想到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 ]
换句话说,析构函数由调用函数调用。