繁体   English   中英

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

[英]How is destructor called for temporary objects returned from a function in C++?

这是来自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";} );
}

我有两个问题:

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

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

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

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

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

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

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

最简单的解决方法是

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) };
}

并用作

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

使用copy-list-initialization和生命周期扩展转发引用可以避免对Final_action对象进行任何复制/移动。 复制列表初始化构建临时Final_action直接返回值,并返回临时finally有其生命周期通过结合扩展act1 -也没有任何复制或移动。

代码坏了。 SimonKraemer提到的修改后的代码被破坏了 - 它没有编译( finally的return语句是非法的,因为Final_action既不可复制也不可移动)。 使用生成的移动构造函数使Final_action只移动也不起作用,因为F保证有一个移动构造函数(如果它没有,那么Final_action生成的移动构造函数将默默使用F的复制构造函数作为后备),移动后F也不保证是无操作。 实际上,示例中的lambda 不会变成no-op。

有一个相对简单和便携的解决方案:

添加标志bool valid = true; Final_action并覆盖移动c'tor并移动赋值以清除源对象中的标志。 仅在valid调用clean() 这可以防止生成复制c'tor和复制分配,因此不需要显式删除它们。 (加分点:将标志放入可重复使用的仅移动包装器中,这样您就不必实现移动控制并移动Final_action赋值。在这种情况下,您也不需要显式删除。)

或者,删除Final_action的模板参数并将其更改为使用std::function<void()> 在调用clean之前检查clean是否为空。 添加move c'tor并移动将原始std::functionnullptr赋值。 (是的,这是必要的可移植。移动std::function不能保证源是空的。)优点:类型擦除的常见好处,例如能够将示波器防护装置返回到外部堆栈框架中没有暴露F 缺点:可能会增加大量的运行时间开销。

在我目前的工作项目中,我基本上ScopeGuard<F>两种方法与ScopeGuard<F>和使用类型擦除功能对象的AnyScopeGuard相结合。 前者使用boost::optional<F> ,可以转换为后者。 作为允许范围保护为空的额外好处,我也可以明确地dismiss()它们。 这允许使用范围保护设置事务的回滚部分,然后在提交时解除它(使用非抛出代码)。

更新:Stroustrup的新示例甚至不编译。 我错过了明确删除副本c'tor也禁用移动c'tor的生成。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM