理解std::vector::push_back(std::move(v[i]))

4

初学者问题。

在下面的代码中,我本来期望v2 [0]=1也会改变v1的值,但事实上并没有。

push_back自C++11(ref)起似乎接受T&&,而且std::move等价于static_cast<T&&>(t);,详见this answer。此外,std::vector ::operator[]返回引用(ref)。 所以我认为v2.push_back(std::move(v1[0]));会将一个引用赋给相同的值。

我错过了什么?我原以为输出结果是11

#include <iostream>
#include <vector>

int main(){
    std::vector<int> v1{5}, v2;
    v2.push_back(std::move(v1[0])); 

    v2[0] = 1;

    std::cout << v1[0] << '\n';
    std::cout << v2[0] << '\n';

    // output:
    // 5
    // 1
}

2
那是因为vector存储的是副本,而不是引用。 - ALX23z
1
@273K v1[0]在移动后具有未指定但有效的状态。不,int类型不会受到“移动”的影响。 - eerorika
1
@eerorika,你对于基本类型是正确的,它们没有移动语义,我的陈述太笼统了。 - 273K
@273K 由于我正在学习C ++,任何“通用”情况对我也有帮助。那么如果它不是基本类型呢...? - starriet
1
相关的问题可能会有所帮助 https://stackoverflow.com/questions/61866784/c-move-semantic-with-integer - 273K
当然,“未指定但有效的状态”包括保留原始值的可能性。 - BoP
2个回答

4
所以我认为v2.push_back(std::move(v1[0]));会引用相同的值。
v1[0]是指向向量的第一个元素的lvalue,而std::move(v1[0])是指向该元素的rvalue。移动与示例的行为几乎无关。
但是,v2的元素不是引用。它们是整数。因此,您通过引用使用第一个元素的值来初始化向量的非引用第二个元素。这类似于以下简单示例:
int a = 5;
int& ref = a;
int b = ref;  // b is not a reference
b = 1;        // this has no effect on a,
              // because it is a separate object from b

std::cout << a << '\n';
std::cout << b << '\n';

1
一个像所有容器一样的向量无法存储引用。 如果你想让v2元素引用v1的某些元素,你可以使用std::reference_wrapper<>
#include <iostream>
#include <vector>
#include <functional>  // for std::reference_wrapper<>

int main() {
   std::vector<int>  v1{ 5 };
   std::vector<std::reference_wrapper<int>>  v2;
   v2.push_back( v1[0] );  // move has no sense, so illegal here

   // v2[0] = 1;   illegal a reference_wrapper is not exactly a reference
   // we must use : v2[0].get() = 1;
   // or            static_cast<int&>(v2[0]) = 1;
   v1[0] = 3;    // possible

   std::cout << v1[0] << ' ' << v2[0] << '\n';

   // output: 3 3
}

请注意,在向量v1中引用元素并不是一个好主意,因为对v1的任何修改都会使所有引用无效。

谢谢。您能详细说明一下“对v1的任何修改都会使所有引用无效”吗? - starriet
1
如果我们在向量中添加或删除某些内容,则向量可能会重新组织,元素地址也会改变。其所有元素的引用或指针都可能变得无效,就算向量被删除也一样。 - dalfaB
哦,你的意思是,如果我们在你的示例代码之后执行 v1.push_back(6),那么 v2[0].get() 不再是 v1[0] 的引用了? - starriet
1
推送后,v2[0]仍然是一个引用,但所引用的位置可能是v1[0],也可能不是。尝试在这个推送之后使用v2[0]将会产生未定义行为(UB)。并非所有容器都有这个缺点。例如std::list<>,请参见https://en.cppreference.com/w/cpp/container/list。 - dalfaB
谢谢。或许原因之一是push_back时重新分配内存的机会吧?如果没有重新分配内存,我不知道为什么引用的位置(地址)会改变。但是,如果这是未定义行为(或者是“未指定”的行为),那可能就取决于实现方案了。感谢您的反馈 :) - starriet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接