【问题标题】:C++ scope guard with zero overhead零开销的 C++ 范围保护
【发布时间】:2021-12-29 17:55:17
【问题描述】:

在 C++ 中,我们可以确保在退出作用域时调用 foo,方法是将 foo() 放入本地对象的析构函数中。当我领导“范围守卫”时,这就是我的想法。有很多通用的实现。

我想知道——只是为了好玩——与在每个退出点写 foo() 相比,是否有可能以零开销实现范围保护的行为。

零开销,我认为:

{
  try {
    do_something();
  } catch (...) {
    foo();
    throw;
  }
  foo();
}

至少 1 个字节的开销来给作用域守卫一个地址:

{
  scope_guard<foo> sg;
  do_something();
}

编译器会优化给sg 一个地址吗?

一个稍微复杂一点的案例:

{
  Bar bar;
  try {
    do_something();
  } catch (...) {
    foo(bar);
    throw;
  }
  foo(bar);
}

{
  Bar bar;
  scope_guard<[&]{foo(bar);}> sg;
  do_something();
}

bar 的生命周期完全包含 sg 的生命周期及其持有的 lambda(以相反的顺序调用析构函数),但 sg 持有的 lambda 仍然必须持有对 bar 的引用。我的意思是,例如 int x; auto l = [&amp;]{return x;}; 在我的 64 位系统上提供 sizeof(l) == 8

是否有一些模板元编程魔法可以在没有任何开销的情况下实现scope_guard 糖?

【问题讨论】:

  • sg 分配有自动存储持续时间 - 这很可能会在堆栈上。您是否遇到了一些堆栈溢出问题,或者您为什么要寻找这样的优化?
  • “至少 1 个字节的开销来给范围保护一个地址” - 看起来像一个模板,因此几乎可以肯定是内联的。你看到什么证据表明 1 字节被分配了?
  • 我想知道第一个“零开销,我认为:”版本是否不是最昂贵的版本,因为它需要“安装”一个额外的异常处理程序。纯属猜测……
  • 我通常只是在godbolt.org 上测试我的假设,然后相信我的编译器。
  • 假设代码没有引入任何未定义/未指定的行为,编译器可以做它喜欢的事情,只要程序产生所需的可观察行为(例如,给定一组输入,产生正确的输出)。这可以包括优化不存在的对象,因此不使用内存来存储该对象。如果代码确实引入了未指定/未定义的行为,那么对编译器所做的限制就更少了(对于未定义的行为,编译器可以忽略这种情况,程序可以终止或重新格式化您的硬盘驱动器)。

标签: c++ scope scopeguard


【解决方案1】:

这里的“零开销”不是很清楚。

编译器会优化给 sg 一个地址吗?

在优化模式下运行时,现代主流编译器很可能会这样做。不幸的是,这是尽可能确定的。它取决于环境,必须经过测试才能可靠。

如果问题是if there is a guaranteed way to avoid &lt;anything&gt; in the resulting assembly,则答案是否定的。正如@Peter 在评论中所说,编译器可以做任何事情来产生等效的结果。它可能根本不会调用foo()即使你把它逐字写在那里 - 当它可以证明观察到的程序行为中的任何内容都不会改变时。

【讨论】:

    猜你喜欢
    • 2011-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-03
    相关资源
    最近更新 更多