>假设我有以下(精简(代码:
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
,因此调用者在调用之前调用复制构造函数。我的问题是,谁负责销毁这个创建的对象?似乎有两个有效的选择:
- 被叫方(即
foo
( 在返回之前对其所有按值参数调用析构函数,然后调用方解除分配内存(通过将其从堆栈中弹出(。
被 - 叫方不做任何事情,而被叫方(即
bar
( 在foo(p)
调用结束时的序列点之前对所有临时变量调用析构函数。
[expr.call]/4 中回答了这个问题,并进行了令人惊讶的详细说明:
。每个参数的初始化和销毁发生在 调用函数。[ 示例:检查构造函数、转换函数或析构函数的访问 在调用函数中的调用点。如果函数参数的构造函数或析构函数引发 例外,对处理程序的搜索从调用函数的范围开始;特别是,如果函数 call 有一个函数 try-block(条款 18(,带有可以处理异常的处理程序,此处理程序不是 考虑。—结束示例 ]
换句话说,析构函数由调用函数调用。
调用方销毁它。请参阅 https://en.cppreference.com/w/cpp/language/lifetime。引用:
所有临时对象都将被销毁,作为评估 (在词法上(包含它们所在的点的完整表达式 已创建,如果创建了多个临时对象,则它们是 以与创造顺序相反的顺序销毁。
也保持这一点作为一般规则 - 一个,谁创造,谁破坏。通常顺序相反。
每当对象的生存期结束时都会调用析构函数,其中包括
范围结束,对于具有自动存储持续时间的对象和 通过绑定到参考文献而延长寿命的临时人员
因此,bar
复制对象的所有者将在复制的对象上调用dtor
。CPP首选项
调用者和被调用者的想法对我来说似乎是错误的。你应该在这里想到scopes
。
在创建函数堆栈的那一刻,P x
foo
的对象存在,该对象将被"创建"。这样,对象最终将通过离开范围来删除,在您的情况下,通过离开函数。
引入新对象的函数中有一个局部作用域,然后又将该作用域保留在同一函数中,在理论上没有区别。
编译器能够"看到"你的对象是如何被使用的,特别是修改的,并且可以通过内联函数跳过"临时"对象的创建,只要代码的行为"好像"写了。
我的简短回答是,这取决于您使用的 ABI。
按照 Eljay 的建议,我在 Linux x86-64 和 Windows 64 上做了一个简单的 clang 实验,以了解为什么我无法生成代码来调用两个 ABI 上的 extern C++ 函数。
下面是代码示例:
class A
{
public:
~A();
};
void foo(A a)
{}
以下是 Linux x86-64 上 foo 函数的 llvm IR
:; Function Attrs: noinline nounwind optnone uwtable mustprogress
define dso_local void @_Z3foo1A(%class.A* %0) #0 {
ret void
}
在Windows 64上也是如此:
; Function Attrs: noinline nounwind optnone uwtable mustprogress
define dso_local void @"?foo@@YAXVA@@@Z"(i8 %0) #0 {
%2 = alloca %class.A, align 1
%3 = getelementptr inbounds %class.A, %class.A* %2, i32 0, i32 0
store i8 %0, i8* %3, align 1
call void @"??1A@@QEAA@XZ"(%class.A* nonnull dereferenceable(1) %2) #2
ret void
}
析构函数显然是由 Windows 64 上的被调用方函数调用的,但在 Linux x86-64 上不是。