Stack unwinding对数组状态恢复的正确性确认及大型数组场景下的性能影响问询
嘿,先给你纠正一个关键的误解——你的代码里的foo是定义在try块外部的,栈展开(stack unwinding)根本不会去“撤销”它的修改!你实际运行这段代码的话,std::cout输出的会是1而不是0,这是第一个要明确的核心点。
咱再把栈展开的本质说透:它的核心工作是销毁try块内部创建的局部对象,并调用这些对象的析构函数,完全不会去回溯修改try块外部对象的状态。举个例子,如果foo是定义在try块里的局部变量:
int main() { try { std::array<int,10> foo = {};// zero-initialize std::fill_n(begin(foo), 10, 1);// assign all ones throw std::runtime_error("intentional"); } catch (const std::exception&) { } // 这里foo已经被栈展开销毁了,根本访问不到 }
这种情况下,栈展开会销毁try块里的foo,但你的代码里foo是main函数的局部变量,从程序启动到结束都在内存里,std::fill_n已经实实在在把它的元素改成了1,异常抛出完全不会让这些修改“凭空消失”。
那你之前的疑问里关于“栈展开自动恢复对象状态”的假设是不成立的,C++的栈展开机制从来不会主动帮你撤销对外部对象的修改——它只管清理try块内的局部资源。
再说到你关心的性能问题:
如果是try块内部的大型数组(比如std::array<int, 1000000>),栈展开时的开销其实远没有你想的那么大。对于std::array这种平凡类型(没有自定义析构函数的类型),栈展开时只是调整栈指针,标记这块内存可以被复用,根本不需要做什么“恢复原始状态”的操作,也不会有备份副本的内存开销。只有当对象有非平凡析构函数时(比如std::vector,它需要释放堆上的内存),栈展开才会调用析构函数做清理,这时候的开销和你正常销毁这个对象的开销完全一致,没有额外的“恢复”成本。
要是你真的需要在异常发生时恢复某个对象的原始状态,那得自己实现逻辑——比如修改前先做一个副本,或者用事务式的修改方式(先改副本,确认无异常再替换原对象),C++标准不会给你自动做这件事。
内容来源于stack exchange

