首页 > 解决方案 > 避免未定义的行为:临时对象

问题描述

我已经编写了一个类,将其用作方便的视图,例如在基于范围的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) {
    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));
}

这是它的预期用途(导致 UB):

std::vector<int> const vector{1, 2, 3};
for (int const value : whole(vector).skip(1)) {
  std::cout << value << ' ';
}

删除该skip(1)部分有帮助,与以下重构相同Range::skip

Range<I> skip(int const amount) const {
  I const new_begin = std::min(begin + amount, end);
  return Range<I>(new_begin, end);
}

似乎临时不能返回对自身的引用。这是 cppreference 所说的:

作为评估完整表达式的最后一步,所有临时对象都被销毁,该完整表达式(词法上)包含创建它们的点,如果创建了多个临时对象,它们将按照与创建顺序相反的顺序被销毁。

虽然我不确定情况是否如此,但我现在不知道如何实际解释它。实际问题是什么,我怎样才能可靠地避免这种 UB?类似的表达例如auto string = std::string("abc").append("def")也不安全吗?

标签: c++undefined-behaviorlifetimeobject-lifetimetemporary-objects

解决方案


基于范围的 for 循环中,范围表达式必须引用为(原型代码)

auto && __range = whole(vector).skip(1) ;

问题是创建的临时whole(vector)对象在完整表达式之后立即被销毁,引用__range(绑定到从返回的引用skip,即临时对象)变得悬空;之后任何取消引用都会导致 UB。

auto string = std::string("abc").append("def")很好,string被复制了,它是一个独立于临时的对象std::string

从 C++20 开始,您可以添加init-statement

如果range_expression返回一个临时值,则它的生命周期会延长到循环结束,如绑定到转发引用__range 所示,但请注意range_expression 内的任何临时值的生命周期都不会延长。

例如

std::vector<int> const vector{1, 2, 3};
for (auto thing = whole(vector); int const value : thing.skip(1)) {
  std::cout << value << ' ';
}

推荐阅读