std::erase() 非引用与引用的行为

Behavior with std::erase() non-references vs reference

假设我有一个大小为 3 的 my_object 类型的向量,我想从我的向量中获取 3 个元素,将它们存储在一个引用中

然后我想使用 std::remove_if() 和 element_1 删除和擦除 element_3,使用 std::remove

删除和删除 element_2

这里是my_object:

  class my_object {
  public:
     my_object(int num);
     bool exists() const;
  private:
     int num;
  };
  my_object::my_object(int num) : num(num) {}
  bool my_object::exists() { return num == 1; }

这里是main:

  std::vector<my_object> my_vector;
  int main() {
     my_object e1(2);
     my_object e2(2);
     my_object e3(1); // i.e exists() will return true in lambda

     my_vector.push_back(e1);
     my_vector.push_back(e2);
     my_vector.push_back(e3);   

     const auto& element_1 = my_vector.at(0);
     const auto& element_2 = my_vector.at(1);
     const auto& element_3 = my_vector.at(2);

     auto lambda = [](auto& src) { return src.exists() };
     std::erase(std::remove_if(b, e, lambda), e); // remove_if for element_3
     std::erase(std::remove(b, e, element_1), e);
     std::erase(std::remove(b, e, element_2), e);   
     return 0;
  }

非常奇怪的是,当我通过引用声明 element_1、element_2、element_3 时,擦除没有正确完成,大小也没有减小到 0 ,但是当我写 const auto 时没有 & 然后它工作得很好,任何人都可以向我解释这种奇怪的行为吗?

不考虑擦除方法,这些引用只是:对对象生活在容器中的引用。一旦 removeremove_if 在序列中向上移动时执行了他们的任务移动分配,这些引用仍然指的是相同的元素,但这些位置的占用者是:

  • 充其量仍然是有效对象,因为有效对象要么留在原处,要么被移动分配到那里。
  • 只是一些以前的自我的外壳,因为引用现在指的是一个从未被目标移动回收的源对象。

我不打算深入 std::remove,而是。看看 std::remove_if

这个相当简单的例子
#include <iostream>
#include <vector>
#include <algorithm>

int main()
{
    std::vector<int> v = { 1,2,3,4,5 };
    const auto& a1 = v.at(0);
    const auto& a2 = v.at(2);
    const auto& a3 = v.at(4);

    std::cout << a1 << ' ' << a2 << ' ' << a3 << '\n';

    std::remove_if(v.begin(), v.end(), [](const auto& x) { return x == 3; });

    std::cout << a1 << ' ' << a2 << ' ' << a3 << '\n';
}

输出

1 3 5
1 4 5

如您所见,std::remove_if 的功能描述与您在代码中看到的一致。 3 元素被删除,4 元素被移动分配到它的位置。您在这里看不到的是 5 元素被移动分配到 4 的位置,而您现在在这里看到的 5 值恰好来自插槽其中 5 。该标准表示该对象是 "valid",但具有 "unspecified" 值。我们可以通过确保我们的移动分配的移动源实际上是 "invalid"(就我们而言)来验证这一点。修改我们的原始程序给我们这个:

#include <iostream>
#include <vector>
#include <algorithm>

struct S
{
    S(int n) : value(n), isvalid(true)
    {
    }

    S(const S& s) : value(s.value), isvalid(true)
    {
    }

    S(S&& s) : value(s.value), isvalid(true)
    {
        s.isvalid = false;
    }

    S& operator =(S&& s)
    {
        value = s.value;
        isvalid = s.isvalid;

        s.isvalid = false;
        return *this;
    }

    int value;
    bool isvalid;
};

std::ostream& operator <<(std::ostream& outp, const S& s)
{
    outp << s.value << '(' << std::boolalpha << s.isvalid << ')';
    return outp;
}

int main()
{
    std::vector<S> v = { 1,2,3,4,5 };
    const auto& a1 = v.at(0);
    const auto& a2 = v.at(2);
    const auto& a3 = v.at(4);

    std::cout << a1 << ' ' << a2 << ' ' << a3 << '\n';

    std::remove_if(v.begin(), v.end(), [](const auto& x) { return x.value == 3; });

    std::cout << a1 << ' ' << a2 << ' ' << a3 << '\n';
}

输出

1(true) 3(true) 5(true)
1(true) 4(true) 5(false)

底线:您的引用仍然指的是与之前相同的插槽,但元素已 (a) 移动分配给其他内容,或 (a) 不再包含指定内容。在执行容器修改时使用对容器内容的引用时要小心。

我保留对 std::erase 电话的评论,因为我根本不知道你在那里做什么。据我所知,这甚至不是标准库中的一个函数(这不是我第一次错过一个新函数,但是 cppreference 没有产生任何结果,所以请按它的价值去做)。