在std::vector的emplace_back中嵌套调用emplace_back是否属于UB?
在向同一std::vector执行emplace_back操作时,若在新元素的构造函数中嵌套调用同一vector的emplace_back添加元素,会出现不同编译器运行结果不一致的情况。以下是最小复现代码:
#include <iostream> #include <vector> struct ComplexObject { // for display only inline static int cpt {}; int indice {}; ComplexObject(std::vector<ComplexObject>& storage, int trick) { indice = cpt; cpt++; if (trick == 0 ) { std::cout << "Creating single Object. Storage size: " << storage.size() << std::endl; } else { std::cout << "Creating double Object. Storage size: " << storage.size() << std::endl; storage.emplace_back(storage, 0); std::cout << "Storage size after double creation : " << storage.size() << std::endl; } } }; int main() { std::vector<ComplexObject> storage; storage.reserve(3); // no realoction storage.emplace_back(storage,0); std::cout << "_________ size after insert simple : " << storage.size()<<"_________" << std::endl; storage.emplace_back(storage,1); std::cout << "_________ size after insert double :" << storage.size() << std::endl; for (auto& e : storage) { std::cout << e.indice; } }
预期最后循环输出为012或021,但Clang和GCC运行结果不一致:
Clang输出
Creating single Object. Storage size: 0 _________ size after insert simple : 1_________ Creating double Object. Storage size: 1 Creating single Object. Storage size: 1 Storage size after double creation : 2 _________ size after insert double :2 02
GCC输出
Creating single Object. Storage size: 0 _________ size after insert simple : 1_________ Creating double Object. Storage size: 1 Creating single Object. Storage size: 1 Storage size after double creation : 2 _________ size after insert double :3 020
两者均出现元素被覆盖的情况,且vector的最终大小存在分歧(推测GCC中最后一个0为垃圾内存)。核心问题:这段代码是否属于未定义行为(UB)?若是,哪部分导致?若不是,哪个编译器存在错误?
解答
这段代码确实属于未定义行为,触发原因如下:
根据C++标准,std::vector::emplace_back的执行流程包含三个关键步骤:
- 确保容器有足够容量(本例中已通过
reserve避免扩容,但步骤依然存在) - 在容器末尾的内存位置构造新元素
- 增加容器的
size()值
当外层emplace_back正在执行步骤2(即ComplexObject的构造函数运行期间),内层嵌套调用同一个vector的emplace_back,会直接修改容器的状态(完成新元素构造并增加size())。此时外层emplace_back的执行环境已经被破坏:它原本准备构造元素的内存位置,可能被内层操作占用或覆盖,而容器的size()也在未完成外层操作时被修改。
C++标准明确规定:容器的修改操作(如emplace_back)执行期间,容器处于不稳定状态,此时嵌套调用同一容器的修改操作,其行为是未定义的——标准不保证这种场景下的执行结果,因此不同编译器可以自由实现,最终导致输出不一致。
即使调用了reserve避免扩容,也无法解决核心问题,因为UB的根源并非扩容导致的迭代器失效,而是修改操作过程中嵌套修改容器状态的行为违反了标准约束。
内容的提问来源于stack exchange,提问作者Martin Morterol

