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

3
我将尝试创建一个函数,模仿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提供的clang编译器并添加-std=c++14标志进行编译后运行,输出结果如下:

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

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

2
只是好奇,为什么你写 auto main() -> int 而不是 int main() - Victor Polevoy
1
你实际上不需要在C++中使用with等价物。 - juanchopanza
4
提示:auto 是按值传递的,它是从右值构造出来的。 - Piotr Skotnicki
@Matt McNabb 啊是的,这是我的疏忽...实际上应该是 foo(), foo(foo&&), during, ~foo(), ~foo(),如果您把这个作为答案发布,我很乐意接受它。 - Sam Kellett
@Vector Polevoy 没有其他原因,只是为了美观。现在随着现代 C++ 的发展,我发现我的一些方法被迫采用新的 auto -> ret-type 格式,为了保持一致性,我更喜欢所有的函数都采用这种格式。 - Sam Kellett
3个回答

7
这里有两个对象:

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

(注意:保留了HTML标签,不做解释)
      1^^^^^^^^^    2^^^^^^

这里有一个由make_foo()返回的对象和函数参数f

如果你按引用传递(改为auto&& f),那么你只会看到一个对象的证据。

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

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


请注意,make_foo()内部可能有更多的对象,但是您的编译器正在执行复制省略。我怀疑复制列表初始化是否涉及任何更多需要编译器省略的临时变量。 - Piotr Skotnicki

3

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

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

搬移构造函数的原因是您的lambda按值接受其参数:
[](auto f) {
// ^^^^^^
    std::cout << "during" << std::endl;
}

如果您不想复制,可以使用引用到常量或甚至是转发引用。

1

通过在lambda函数参数中接受r-reference来防止复制,这对我很有效:

#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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接