7

假设我有一个管理指向内部缓冲区的指针的类:

class Foo
{
  public:

  Foo();

  ...

  private:

  std::vector<unsigned char> m_buffer;
  unsigned char* m_pointer;
};

Foo::Foo()
{
  m_buffer.resize(100);
  m_pointer = &m_buffer[0];
}

现在,假设我还正确实现了规则 3 的东西,包括复制内部缓冲区的复制构造函数,然后将指针重新分配给内部缓冲区的新副本:

Foo::Foo(const Foo& f) 
{
  m_buffer = f.m_buffer;
  m_pointer = &m_buffer[0];
}

如果我还实现了移动语义,那么只复制指针并移动缓冲区是否安全?

Foo::Foo(Foo&& f) : m_buffer(std::move(f.m_buffer)), m_pointer(f.m_pointer)
{ }

在实践中,我知道这应该可行,因为std::vector移动构造函数只是移动内部指针——它实际上并没有重新分配任何东西,所以m_pointer仍然指向一个有效的地址。但是,我不确定标准是否能保证这种行为。移动语义是否std::vector保证不会发生重新分配,因此指向向量的所有指针/迭代器都是有效的?

4

4 回答 4

5

我会再做&m_buffer[0]一次,只是为了让你不必问这些问题。这显然不是很直观,所以不要这样做。而且,在这样做的过程中,您不会有任何损失。双赢。

Foo::Foo(Foo&& f)
   : m_buffer(std::move(f.m_buffer))
   , m_pointer(&m_buffer[0])
{}

我对它很满意,主要是因为它是对成员m_pointer看法m_buffer,而不是严格意义上的成员。

这一切都在回避问题……为什么会在那里?你不能公开一个成员函数给你&m_buffer[0]吗?

于 2013-07-26T13:19:13.060 回答
5

我不会评论 OP 的代码。我所做的只是回答这个问题:

std::vector 移动语义是否保证不会发生重新分配,因此指向向量的所有指针/迭代器都是有效的?

是的移动构造函数。它具有恒定的复杂性(如 23.2.1/4,表 96 和注释 B 中所指定),因此,实现除了从原始内存中窃取内存vector(因此不会发生内存重新分配)并清空原始内存之外别无选择vector

对于移动赋值运算符,否。该标准仅要求线性复杂度(如上述相同段落和表格中所述),因为有时需要重新分配。但是,在某些情况下,它可能具有恒定的复杂性(并且不执行重新分配),但这取决于分配器。(您可以在此处阅读Howard Hinnant关于moved vectors 的精彩论述。)

于 2013-07-26T13:03:32.917 回答
2

更好的方法可能是:

class Foo
{

  std::vector<unsigned char> m_buffer;
  size_t m_index;

  unsigned char* get_pointer() { return &m_buffer[m_index];
};

即,与其存储指向向量元素的指针,不如存储它的索引。这样,它将不受向量后备存储的复制/调整大小的影响。

于 2013-07-26T11:58:51.883 回答
1

move构造的情况是保证缓冲区从一个容器移动到另一个容器,所以从新创建的对象来看,操作是没问题的。

另一方面,你应该小心使用这种代码,因为捐赠者对象留下了一个空向量和一个指向不同对象中向量的指针。这意味着从对象移出后处于脆弱状态,如果有人访问接口,甚至更重要的是,如果析构函数尝试使用指针,则可能会导致问题。

虽然通常在被移出后不会使用您的对象(假设要被右值引用绑定,它必须是右值),但事实是您可以通过强制转换从左值中移出或通过使用std::move(这基本上是一个演员),在这种情况下,代码实际上可能会尝试使用您的对象。

于 2013-07-26T13:09:28.940 回答