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