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");