C++未赋值接收函数返回值时临时对象构造能否被优化消除
C++中未接收函数返回值时,构造函数的副作用能否被编译器优化消除
在如下示例中:
struct Foo { Foo() { std::cout << "Foo constructor called\n"; } }; Foo getFoo() { return Foo(); } int main() { Foo foo = getFoo(); }
由于复制消除(copy elision)机制的存在,我们无法确定构造函数会被调用一次还是两次:
复制消除是C14标准前唯一允许改变程序可观测副作用的优化形式;自C14起,它与分配消除及扩展(allocation elision and extension)并列为两种允许改变可观测副作用的优化形式。
因此这类场景下程序的副作用执行结果是无法被依赖的。如果返回值被赋值接收,我们可以确定拷贝构造函数至少会被调用一次。但如果调用getFoo()时(甚至是意外误调用)没有赋值接收返回值,编译器是否可以完全不构造对应的对象?实际开发中遇到过如下侵入式智能指针的实现场景,就涉及这个问题:
template <typename T> struct IntrusivePointer { IntrusivePointer(T* ptr) : ptr(ptr) { if (ptr) ptr->increment_ref_count(); } T* ptr = nullptr; ~IntrusivePointer() { if (ptr) ptr->decrement_ref_count(); } }; class Thing { public: int ref_count = 0; void increment_ref_count() { ++ref_count; } void decrement_ref_count() { --ref_count; if (ref_count == 0) { recycle(); } } void recycle(); }; struct Manager { static inline std::vector<Thing*> availableThings; static inline int num_things_used = 0; /* 如果这个函数被调用但返回值没有被接收,整个引用计数系统会不会崩溃? */ IntrusivePointer<Thing> getThing() { Thing* thing = availableThings.back(); availableThings.pop_back(); ++num_things_used; /* 就算返回值没被接收,按逻辑下面的临时对象也应该被构造: - 只要有任意一个IntrusivePointer对象被构造,最终引用计数归零后对象就会被回收 - 但我不确定这个对象的构造过程会不会被编译器完全优化掉 */ return IntrusivePointer<Thing>(thing); } static void recycle(Thing* thing) { availableThings.push_back(thing); --num_things_used; } }; inline void Thing::recycle() { Manager::recycle(this); }
针对这个场景的结论如下:
- 如果返回值没有被赋值接收,严格来说不存在复制消除的适用场景,任何消除
IntrusivePointer构造副作用的优化都不属于复制消除的合法覆盖范围。 - 编译器确实可能检测到返回值未被使用,从而尝试优化掉无关逻辑,但只要对象构造存在可观测的副作用,且不符合复制消除或分配消除与扩展的适用条件,编译器无权消除构造函数产生的副作用,上述侵入式指针的引用计数增减、对象回收逻辑,不会因为返回值未被接收就被优化跳过。
内容的提问来源于stack exchange,提问作者Zebrafish
相关产品推荐
相关产品推荐

