对象移动后,指向 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";
}
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_
}
像这样移动后,您的新实例有效地窃取了原始实例的所有内容,并且它“稳定有效”。另一方面,原来的现在是空的。
考虑以下 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";
}
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_
.
要解决这个问题,您可以定义自己的移动构造函数,而不是让编译器生成它:
A::A(A&& other)
: vec_(std::move(other.vec_))
, b_(other.b_)
{
other.b_ = nullptr; // effectively steal everything from 'other', including it's b_
}
像这样移动后,您的新实例有效地窃取了原始实例的所有内容,并且它“稳定有效”。另一方面,原来的现在是空的。