C++14与C++17中std::vector元素赋值函数返回值的行为差异
C14与C17中持久化线段树赋值行为差异的原因
问题描述
我编写了一段包含持久化线段树的C代码,当直接将update函数的返回值赋值给std::vector<Node>的元素st[cur].left时,在C14环境运行会触发断言失败(断言st[cur].left != 0不成立),但在C17及C20环境中运行正常。若先将返回值存入临时变量再赋值则无问题。
代码示例
#include<bits/stdc++.h> const int MaxN = 1e5 + 10; struct Node{ long long sum; int left,right; Node() :sum(0),left(0),right(0){ } Node(long long sum,int left,int right) :sum(sum),left(left),right(right){ } }; struct PersistentSegmentTree{ std::vector<Node> st; std::vector<int> ver; int n; PersistentSegmentTree(int n) :n(n){ ver.push_back(0); st.push_back(Node()); } int update(int l,int r,int pos,int val,int oldId){ std::cerr << l << " " << r << "\n"; st.push_back(Node()); if(l == r){ st.back() = Node(val,0,0); assert((int)st.size() - 1 != 0); return (int)st.size() - 1; } int cur = (int)st.size() - 1; std::cerr << cur << "\n"; int mid = (l + r) >> 1; if(pos <= mid){ st[cur].left = update(l,mid,pos,val,st[oldId].left); // <---- C++14 / C++17 行为差异点 // 以下代码逻辑相同但无问题 // int t = update(l,mid,pos,val,st[oldId].left); // st[cur].left = t; assert(st[cur].left != 0); // <---- C++14下断言失败 st[cur].right = st[oldId].right; }else{ st[cur].left = st[oldId].left; st[cur].right = update(mid + 1,r,pos,val,st[oldId].right); } st[cur].sum = st[st[cur].left].sum + st[st[cur].right].sum; assert(cur != 0); return cur; } long long getSum(int l,int r,int u,int v,int id){ if(l > v || r < u){ return 0; } if(u <= l && r <= v){ return st[id].sum; } int mid = (l + r) >> 1; return getSum(l,mid,u,v,st[id].left) + getSum(mid + 1,r,u,v,st[id].right); } void updateArray(int arrayIndex,int pos,int val){ ver[arrayIndex] = update(1,n,pos,val,ver[arrayIndex]); std::cerr << arrayIndex << " " << ver[arrayIndex] << " " << st[ver[arrayIndex]].left << "\n"; } void copyArray(int arrayIndex){ ver.push_back(ver[arrayIndex]); } long long sumQuery(int arrayIndex,int l,int r){ return getSum(1,n,l,r,ver[arrayIndex]); } }; int a[MaxN + 1],n; void readData(){ std::cin >> n; for(int i = 1;i <= n;++i){ std::cin >> a[i]; } } void query(){ PersistentSegmentTree tree(n); tree.copyArray(0); for(int i = 1;i <= n;++i){ tree.updateArray(1,i,a[i]); } std::cout << tree.sumQuery(1,1,1); } int main(){ std::ios_base::sync_with_stdio(false); std::cin.tie(nullptr);std::cout.tie(nullptr); PersistentSegmentTree tree(3); tree.copyArray(0); tree.updateArray(1,1,2); std::cout << tree.sumQuery(1,1,1); return 0; }
差异原因解析
1. C++标准对表达式求值顺序的规则变化
- C++14及更早版本:在赋值表达式
st[cur].left = update(...)中,编译器可以自由选择先求值左侧的st[cur],还是先调用右侧的update函数。如果编译器先计算st[cur]得到一个引用,随后update函数内执行st.push_back(Node())会触发std::vector的内存重新分配——这会导致之前获取的st[cur]引用失效,后续赋值操作会写入一块已被释放的内存,最终st[cur].left的值变成无效的0,触发断言失败。 - C++17及以上版本:标准明确规定,赋值表达式
a = b必须先完整求值右侧的b,再求值左侧的a。因此会先执行完update函数的所有逻辑(包括完成push_back、确保vector内存稳定),再获取st[cur]的有效引用进行赋值,不会出现引用失效的问题。
2. 临时变量规避问题的原理
当使用临时变量int t = update(...); st[cur].left = t;时,无论哪个C++标准,都会先执行update函数完成所有push_back操作,待vector内存稳定后,再计算st[cur]获取有效引用,因此赋值操作不会出错。
内容的提问来源于stack exchange,提问作者HuyAT
相关产品推荐
相关产品推荐

