C++ 避免未定义的行为:临时对象
我编写了一个类,将其用作一个方便的视图,例如,在基于范围的C++ 避免未定义的行为:临时对象,c++,undefined-behavior,lifetime,object-lifetime,temporary-objects,C++,Undefined Behavior,Lifetime,Object Lifetime,Temporary Objects,我编写了一个类,将其用作一个方便的视图,例如,在基于范围的fors中。总的来说,它只是一对带边界检查的迭代器: template<typename I> class Range { private: I begin; I end; public: Range(I const begin, I const end) : begin(begin), end(end) {} Range<I>& skip(int const amount)
for
s中。总的来说,它只是一对带边界检查的迭代器:
template<typename I> class Range {
private:
I begin;
I end;
public:
Range(I const begin, I const end)
: begin(begin), end(end)
{}
Range<I>& skip(int const amount) {
begin = std::min(begin + amount, end);
return *this;
}
};
template<typename C> auto whole(C const& container) {
using Iterator = decltype(std::begin(container));
return Range<Iterator>(std::begin(container), std::end(container));
}
这似乎是一个暂时的不能返回自己的引用。以下是cppreference所说的:
所有临时对象都将被销毁,这是计算(词汇上)包含创建它们的点的完整表达式的最后一步,如果创建了多个临时对象,它们将按照与创建顺序相反的顺序销毁
虽然我不确定这是不是真的,但我现在不知道如何实际地解释它。实际问题是什么?我如何才能可靠地避免这种情况?类似的表达式,例如
auto string=std::string(“abc”).append(“def”)
也不安全吗?关于std::string
,append
函数返回对其所附加字符串的引用。如果使用字符串对象,则在字符串对象被破坏后保留该引用将导致未定义的行为
但是,如果复制字符串对象,则是安全的,因为您将拥有字符串的副本,而不是对不存在对象的引用:
std::string s = std::string("abc").append("def");
这里的s
将是一个副本,通过副本初始化进行初始化(它传递std::string(“abc”)。将s
的副本构造函数附加(“def”)
,临时对象将在该构造函数中始终有效)
至于
for (int const value : whole(vector).skip(1)) { ... }
如果Range
类被修改为iterable(您需要begin
和end
函数来返回迭代器),那么它仍然不可用
这是因为这样的循环范围对应于
for (auto iter = whole(vector).skip(1).begin();
iter != whole(vector).skip(1).end();
++iter)
{
...
}
Range
类不包含向量的副本,它包含向量迭代器的副本(对于所示示例)。在临时范围
对象被销毁之前,将复制或使用这些迭代器。范围表达式中的对象被绑定为引用as(原型代码)
问题是由整体(向量)
创建的临时在完整表达式后立即被销毁,引用\uu范围
(绑定到从跳过
返回的引用,即临时)变为挂起;在此之后,对它的任何取消引用都会导致UB
auto string=std::string(“abc”).append(“def”)
很好,string
被复制,它是临时std::string
的独立对象
从C++20开始,您可以添加:
如果range_表达式返回一个临时表达式,则其生存期将延长到循环结束,如绑定到转发引用\uu range所示,但请注意range_表达式内的任何临时表达式的生存期都不会延长
例如
std::vector const vector{1,2,3};
for(auto thing=full(vector);int const值:thing.skip(1)){
std::cout的范围包含对该范围的引用
for (int const value : whole(vector).skip(1)) {
std::cout << value << ' ';
}
它是安全的,因为临时的只需要比构造函数的寿命长
另外,我更喜欢范围跳过(int)常量
,而不是变异的。你的“类循环”也会禁止整个(向量)
,这很好。哦,对不起,我没有写begin()和end()在这个例子中,我解决它的方法是对包含容器的某个对象进行条件继承,然后将其移动到该对象。因此,如果使用const进行构造&使用引用,如果使用&进行构造,则存储引用。我很久以前就这样做了,但现在使用std::conditional\u t,如果constexpr,自动返回类型是这样的
for (auto iter = whole(vector).skip(1).begin();
iter != whole(vector).skip(1).end();
++iter)
{
...
}
auto && __range = whole(vector).skip(1) ;
std::vector<int> const vector{1, 2, 3};
for (auto thing = whole(vector); int const value : thing.skip(1)) {
std::cout << value << ' ';
}
for (int const value : whole(vector).skip(1)) {
std::cout << value << ' ';
}
{
auto && __range = whole(vector).skip(1);
auto __begin = __range.begin();
auto __end = __range.end();
for ( ; __begin != __end; ++__begin) {
int const value = *__begin;
std::cout << value << ' ';
}
}
std::string s = std::string("abc").append("def");