阅读“C++编程语言”(第4版)的“异常处理”一章中,有一个特定的临时清除代码帮助器示例:

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代码。

我认为这在Stroustrup进行测试时适用于Stroustrup,这是因为“返回值优化”将Final_action<>限制为单个实例-但是RVO是否只是可选的优化?如果实例从最终返回时被复制,则显然~Final_action()对两个拷贝都运行。换句话说,p被删除两次。

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

最佳答案

实际上,该示例依赖于复制省略号,从C++ 17开始(在某些情况下)仅能保证复制。

话虽如此,复制省略是一种在大多数现代C++ 11/C++ 14编译器中实现的优化。如果该代码段在优化的构建中失败,我会感到惊讶。

但是,如果您想使其防弹,则可以添加一个move构造函数:

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()上)。启用优化后,编译器将更加进取:仅启用-O1即可查看gcc 4.7.1的输出:
main:
  mov eax, 1 # since i is incremented only once, the return value is 1.
  ret

关于c++ - 依靠RVO实现最终功能,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/49632756/

10-13 08:18