标准不需要编译器执行返回值优化(RVO),但是从C ++ 11开始,必须移动结果。
看来,这可能会将UB引入到/破坏代码中,这在C ++ 98中是有效的。
例如:
#include <vector>
#include <iostream>
typedef std::vector<int> Vec;
struct Manager{
Vec& vec;
Manager(Vec& vec_): vec(vec_){}
~Manager(){
//vec[0]=42; for UB
vec.at(0)=42;
}
};
Vec create(){
Vec a(1,21);
Manager m(a);
return a;
}
int main(){
std::cout<<create().at(0)<<std::endl;
}
Run Code Online (Sandbox Code Playgroud)
当使用gcc(或与此相关的clang)进行编译时(为了简化示例,-O2 -fno-inline -fno-elide-constructors
我正在使用std::vector
这些build-option。如果没有这些选项以及手工类和更复杂的create
功能,则可能会触发相同的行为)对于C ++ 98(-std=c++98
)一切正常:
return a;
触发复制构造函数,它保持a
原样。m
称为(必须在a
被销毁之前发生,因为它m
是在之后构造的a
)。a
在析构函数中访问是没有问题的。a
函数称为。结果与预期的一样:21
已打印(此处为live)。
但是,使用C ++ 11(-std=c++11
)构建时,情况则有所不同:
return a;
触发移动构造器,该构造器“销毁” a
。m
被调用,但是现在访问a
是有问题的,因为a
已被移动并且不再完整。vec.at(0)
现在抛出。这是一个现场演示。
我是否缺少某些内容,并且示例在C ++ 98中也是有问题的?
这不是一个重大变化。你的代码在 C++98 中已经注定了。想象一下你有
int main(){
Vec v;
Manager m(v);
}
Run Code Online (Sandbox Code Playgroud)
在上面的示例中,当 被销毁时,您访问向量m
,并且由于向量为空,因此抛出异常(如果使用 则有 UB []
)。vec
这与您从回来时遇到的情况相同create
。
这意味着您的析构函数不应该对其类成员的状态做出假设,因为它不知道它们处于什么状态。要使您的析构函数对于任何版本的 C++ 都“安全”,您需要将调用at
放入try-catch 块,或者您需要测试向量的大小以确保它等于或大于您的预期。
归档时间: |
|
查看次数: |
328 次 |
最近记录: |