对象移动后,指向 const vector 成员变量元素的指针是否保持稳定?

Do pointers to const vector member variable elements remain stable after the object is moved?

考虑以下 C++ 代码:

class B { ... }

class A {
  public:
    // Requires vec.size() >= 1
    A(std::vector<B> vec) : vec_(vec), b_(&vec_[0]) {}
    const std::vector<B> vec_;
    const B* b_;
}

如果我std::move一个类型A的对象,甚至复制它,我指向B对象的指针b_是否会保持稳定和有效?我以为它会是因为 vec_ 是常量,但事实并非如此。

您的代码无法编译,因为您使用的是 Class 而不是 class,并且因为 std::vector<B> 无法使用 B 类型的数据进行初始化。 See std::vector ctors.

现在,无法移动常量数据。如果你使用类似的东西 A a1 = std::move(a)a 是常量,将调用复制构造函数而不是移动构造函数,因为 a 是常量。

您希望如何从常量数据移动?移动意味着窃取数据的内容(这里是常量)并使其处于有效状态。窃取意味着您正在更改不变的数据!

现在,由于您的元素不会通过从 A 移动而移动(因为它没有移动构造函数),您的指针仍将指向您的数据。

但是如果你这样做,你将在新的A中有相同的指针但不同的向量,如下

#include <vector>
#include <iostream>

class B { };

class A {
public:
    // Requires vec.size() >= 1
    A(std::vector<B> vec) : vec_(vec), b_(&vec_[0]) {}
    const std::vector<B> vec_;
    const B* b_;
};

int main(){
    std::vector<B> vec(1, B{});
    A a(vec);
    A a1 = std::move(a);
    if(a.b_ == a1.b_ && &a.vec_[0] != &a1.vec_[0])
        std::cout << "two pointers to the same location while the reality doesn't agree";

}

Live

TLDR;您需要删除 const 并实现您自己的移动构造函数/移动赋值运算符。 (可能在本 post 末尾实施)。

说明

当您不定义移动或复制构造函数时,编译器将尝试为您生成它们,除非您给出不这样做的理由,例如声明自定义构造函数或具有不可复制的数据成员。

虽然这些构造函数并不神奇,但写下编译器可能生成的内容有助于理解会发生什么:

A::A(A&& other)
    : vec_(std::move(other.vec_)) // Would call vector(vector&&), if vec_ wasn't const
    , b_(std::move(other.b_))     // POD types are not moved, so this copies b_
{}

你有一个普通的旧数据成员const B b_;。 POD 类型不会被移动,而是被复制。这不是您想要的,因此我们将在下面的移动构造函数中修复它。

由于std::vector定义了它自己的移动构造函数,vec_(std::move(other.vec_));移动向量的内容,除非它被const阻止它在这里

假设这不是您的问题的重点,并且您确实想要移动:您从两个数据成员中删除了const,结果如下:

b_(other.b_) 保持不变(副本),但现在我们可以调用 std::vector 的移动构造函数。我们来看一个简化版的vector(vector&&):

template <class T>
vector<T>::vector(vector&& other)
{
    this->__begin_ = __x.__begin_;       // Take other's begin
    this->__end_ = __x.__end_;           // Take other's end
    __x.__begin_ = __x.__end_ = nullptr; // REMOVE the begin/end from 'other'
}

所以 你原来的 A 实例以一个空的 vec_ 结束,它的 b_ 指向新实例的 vec_[0] !如果您使用现在移动的原始实例中的 b_,您将在新实例的 vec_.

中成为 reading/writing

要解决这个问题,您可以定义自己的移动构造函数,而不是让编译器生成它:

A::A(A&& other)
    : vec_(std::move(other.vec_))
    , b_(other.b_)
{
    other.b_ = nullptr; // effectively steal everything from 'other', including it's b_
}

像这样移动后,您的新实例有效地窃取了原始实例的所有内容,并且它“稳定有效”。另一方面,原来的现在是空的。