析构函数如何从C++中的函数返回临时对象?

bts*_*eng 16 c++ raii

这是来自Stroustrup的"C++编程语言"的代码,该代码实现了一个finally我无法理解析构函数被调用的地方.

template<typename F> struct Final_action
{
  Final_action(F f): clean{f} {}
  ~Final_action() { clean(); }
  F clean;
}

template<class F> 
Final_action<F> finally(F f)
{
  return Final_action<F>(f);
}

void test(){
  int* p=new int{7};
  auto act1 = finally( [&]{delete p;cout<<"Goodbye,cruel world\n";} );
}
Run Code Online (Sandbox Code Playgroud)

我有两个问题:

  1. 根据作者的说法,delete p只有一次被调用:当act1超出范围时.但是从我的理解:首先,act1将使用复制构造函数初始化,然后Final_action<F>(f)函数中的临时对象finally被破坏,delete p第一次调用,然后在函数结束test时第二次act1超出范围.我哪里弄错了?

  2. 为什么finally需要这个功能?我不能只定义Final_action act1([&]{delete p;cout<<"Goodbye,cruel world\n"})?那是一样的吗?

此外,如果有人能想到更好的标题,请修改当前的标题.

更新:在进一步思考之后,我现在确信析构函数可能会被调用三次.另外一个是用于在调用函数中自动生成的临时对象,void test()用作复制构造函数的参数act1.这可以通过-fno-elide-constructorsg ++中的选项进行验证.对于那些和我有同样问题的人,请参阅Bill Lynch在答案中指出的复制省略以及返回值优化.

Bil*_*nch 13

你是对的,这段代码坏了.它仅在应用返回值优化时才能正常工作.这一行:

auto act1 = finally([&]{delete p;cout<<"Goodbye,cruel world\n"})
Run Code Online (Sandbox Code Playgroud)

可能会也可能不会调用复制构造函数.如果是,那么你将有两个类型的对象,Final_action因此你将两次调用lambda.

  • 看起来这一章得到了更新:[这里](https://books.google.de/books?id=kCF4BgAAQBAJ&pg=PA358&dq=struct+Final_action&hl=de&sa=X&ved=0CDEQ6AEwAmoVChMIztbthqT-xwIVpg9yCh213QFD#v=onepage&q=struct%20Final_action&f= false)vs [Here](https://books.google.de/books?id=PSUNAAAAQBAJ&pg=PA358&dq=struct+Final_action&hl=de&sa=X&ved=0CCkQ6AEwAWoVChMIztbthqT-xwIVpg9yCh213QFD#v=onepage&q=struct%20Final_action&f=false) (3认同)
  • @SimonKraemer更新的代码也不正确,请参阅Arne Vogel的答案. (2认同)

T.C*_*.C. 5

最简单的解决方法是

template<typename F> 
struct Final_action
{
  Final_action(F f): clean{std::move(f)} {}
  Final_action(const Final_action&) = delete;
  void operator=(const Final_action&) = delete;
  ~Final_action() { clean(); }
  F clean;
};

template<class F> 
Final_action<F> finally(F f)
{
  return { std::move(f) };
}
Run Code Online (Sandbox Code Playgroud)

并用作

auto&& act1 = finally( [&]{delete p;cout<<"Goodbye,cruel world\n";} );
Run Code Online (Sandbox Code Playgroud)

使用copy-list-initialization和生命周期扩展转发引用可以避免Final_action对象的任何复制/移动.复制列表初始化Final_action直接构造临时返回值,并且返回的临时值finally通过绑定延长其生命周期act1- 也无需任何复制或移动.