依靠RVO最终功能

时间:2018-04-03 14:35:46

标签: c++ c++11

阅读 C ++编程语言(第4版),在“异常处理”一章中,有一个 ad hoc 清理代码的示例助手:

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

它像

一样使用
auto act1 = finally([&]{ delete p; });

在声明act1的块的末尾运行lambda代码。

由于返回值优化Final_action<>限制为单个实例,我认为这对Stroustrup来说是有用的,但是RVO只是一个可选的优化吗?如果在从finally返回时复制实例,则显然~Final_action()运行两个副本。换句话说,p被删除两次。

标准中的某些内容是否阻止了这种行为,或者代码是否足够简单以便大多数编译器对其进行优化?

2 个答案:

答案 0 :(得分:2)

实际上,这个例子依赖于复制椭圆,这只能保证(在某些环境中),因为C ++ 17。

话虽如此,copy ellision是一种在大多数现代C ++ 11 / C ++ 14编译器中实现的优化。如果此片段在优化版本上失败,我会感到惊讶。

但是,如果你想让它变得无懈可击,你可以添加一个移动构造函数:

template<typename F>
struct Final_action {
    Final_action(F f): clean{f} {}
    ~Final_action() { if (!moved) clean(); }
    Final_action(Final_action&& o) : clean(std::move(o.clean)) {o.moved = true;}
private:
    F clean;
    bool moved{false};
};

template<class F>
Final_action<F> finally(F f)
{
    return Final_action<F>(f);
}
但是,我不认为这是必要的。实际上,即使您不启用优化,大多数编译器也会复制椭圆。 gcc clang icc MSVC 都是这方面的例子。这是因为标准明确允许复制椭圆。

如果您添加此代码段:

int main() {
    int i=0;
    {
        auto f = finally([&]{++i;});
    }
    return i;
}

并分析godbolt.org上生成的程序集输出,您会看到Final_action::~Final_action()通常只调用一次(在main()上)。启用优化后,编译器会更具攻击性:检查 gcc 4.7.1 的输出,仅启用-O1

main:
  mov eax, 1 # since i is incremented only once, the return value is 1.
  ret

答案 1 :(得分:0)

这仅适用于C ++ 17!由于删除了复制构造函数,因此使用C ++ 11或C ++ 14会失败。由于C ++ 17有circumstances which enforce RVO,因此不需要复制/移动构造函数。

  

如果复制了实例[..]

那么,如何禁止复制?

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

如果您已经使用了提升,则来自boost::noncopyable

Further discussion about preventing copies.

#include <iostream>

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

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

int main() {
  auto _ = finally([]() { std::cout << "Bye" << std::endl; });
}