【问题标题】:Do pointers to const vector member variable elements remain stable after the object is moved?移动对象后,指向 const 向量成员变量元素的指针是否保持稳定?
【发布时间】:2020-11-12 13:57:10
【问题描述】:

考虑以下 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_ 是 const,但事实并非如此。

【问题讨论】:

  • 您的新构造函数,采用std::vector&lt;B&gt; 而不是B 是有风险的。如果用户提供一个空向量怎么办?
  • 我明白,但这是一个关于指针稳定性的玩具示例,所以希望不变的注释有效。
  • 如果您存储一个指向对象的指针,只要该对象继续存在,该指针就会继续指向该对象。这包括如果您离开它,因为该对象仍然存在。如果你复制它也一样。

标签: c++ pointers vector constants move-semantics


【解决方案1】:

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

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

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

现在,由于从 A 移动不会移动您的元素(因为它没有移动 ctor),所以您的指针仍将指向您的数据。

但如果你这样做,你将在新的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

【讨论】:

  • 我的错,我已经修正了类错字。为什么 std::vector 不能用 B 类型初始化?
  • @Max 因为std::vector&lt;B&gt; 没有构造函数接受B
  • 没有构造函数。你可以用vec_(1, b) 来做。见constructors for std::vector
  • 对,我认为您可以像这样对初始化列表中的向量使用聚合初始化,但我猜不是。我也解决了这个问题,但我的核心问题仍未得到解答。
  • @Max 实际上,vec_{b} 就像您在问题的第一个版本中所说的那样可以编译,但 vec_(b) 不会。然而,非const B* b_ 不会让你编译程序。
【解决方案2】:

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

说明

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

这些构造函数并不是什么神奇的东西,所以写下编译器可能生成的内容可以帮助理解会发生什么:

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_
}

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

【讨论】:

    猜你喜欢
    • 2014-10-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-02
    • 2012-11-07
    • 1970-01-01
    • 2013-04-21
    相关资源
    最近更新 更多