关于c ++:依靠RVO最终功能

Relying on RVO for finally function

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

1
2
3
4
5
6
7
8
9
10
11
12
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);
}

就像

1
auto act1 = finally([&]{ delete p; });

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

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

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


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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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都是此类示例。这是因为该标准明确允许复制省略。

如果添加此代码段:

1
2
3
4
5
6
7
int main() {
    int i=0;
    {
        auto f = finally([&]{++i;});
    }
    return i;
}

并在godbolt.org上分析生成的程序集输出,您将看到Final_action::~Final_action()通常仅被调用一次(在main()上)。启用优化后,编译器将更加进取:仅启用-O1即可检查gcc 4.7.1的输出:

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

自C ++ 17起这才有效!对于C ++ 11或C ++ 14,由于删除了副本构造函数,因此它失败。从C ++ 17开始,在某些情况下会强制执行RVO,因此不需要复制/移动构造函数。

If the instance is copied [..]

好吧,不允许复制吗?

1
2
3
4
5
6
7
8
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派生(如果您已经在使用boost)。

有关防止复制的进一步讨论。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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; });
}