完美转发,为什么析构函数被调用两次

Perfect forwarding, why does the destructor get called twice?

本文关键字:两次 调用 转发 为什么 析构函数 完美      更新时间:2023-10-16

我试图使一个函数模仿Python的with语句,但我遇到了一些有趣的行为,我不太理解。

使用以下程序:

#include <iostream>
struct foo {
  foo() { std::cout << "foo()" << std::endl; }
  ~foo() { std::cout << "~foo()" << std::endl; }
};
auto make_foo() -> foo {
  return {};
}
template <typename T, typename F>
auto with(T&& t, F&& fn) -> void {
  fn(std::forward<T>(t));
}
auto main() -> int {
  std::cout << "before" << std::endl;
  with(make_foo(), [](auto f) {
    std::cout << "during" << std::endl;
  });
  std::cout << "after" << std::endl;
}

在Xcode 6.3和-std=c++14提供的clang下编译并运行时,我得到以下输出:

before
foo()
during
~foo()
~foo()
after

有人知道为什么我的输出中有两个~foo()吗?

下面是两个对象:

with(make_foo(), [](auto f) {

      1^^^^^^^^^    2^^^^^^

make_foo()返回的对象和函数参数f

如果您通过引用传递(更改为auto&& f),那么您将只看到一个对象的证据。

没有创建消息,因为这是通过复制/移动构造创建的,并且在这些构造函数中没有任何输出。

请注意,make_foo()内部可能有更多的对象,但编译器正在执行复制省略

你的析构函数调用似乎与构造函数调用不匹配,仅仅是因为你没有跟踪复制/移动构造函数。如果我们像这样添加跟踪:

struct foo {
  foo() { std::cout << "foo()" << std::endl; }
  ~foo() { std::cout << "~foo()" << std::endl; }
  foo(const foo&) { std::cout << "foo(const foo&)" << std::endl; }
  foo(foo&&) { std::cout << "foo(foo&&)" << std::endl; }
};

我们现在的输出是:

before
foo()
foo(foo&&)
during
~foo()
~foo()
after

move构造的原因是您的lambda按值接受其参数:

[](auto f) {
// ^^^^^^
    std::cout << "during" << std::endl;
}

如果不需要复制,可以通过引用到const,甚至转发引用。

通过在lambda函数参数中接受r-引用来防止复制,这对我来说是有效的:

#include <iostream>
struct foo {
  foo() { std::cout << "foo()" << std::endl; }
  ~foo() { std::cout << "~foo()" << std::endl; }
};
auto make_foo() -> foo {
  return {};
}
template <typename T, typename F>
auto with(T&& t, F&& fn) -> void {
  fn(std::forward<T>(t));
}
auto main() -> int {
  std::cout << "before" << std::endl;
  with(make_foo(), [](auto&&) { // r-reference!
    std::cout << "during" << std::endl;
  });
  std::cout << "after" << std::endl;
}

新改进的输出:

before
foo()
during
~foo()
after