使用指向内部缓冲区的指针移动语义

时间:2013-07-26 11:43:40

标签: c++ c++11 vector move-semantics

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

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 个答案:

答案 0 :(得分:5)

我不会评论OP的代码。我正在做的就是回答这个问题:

  
    

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

  

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

否移动赋值运算符。该标准仅需要线性复杂度(如上面提到的相同段落和表中所指定的),因为有时需要重新分配。但是,在某些情况下,它可能具有恒定的复杂性(并且不执行重新分配),但它取决于分配器。 (您可以通过Howard Hinnant here阅读移动的vector上的优秀博览会。)

答案 1 :(得分:4)

我会再次&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]吗?

答案 2 :(得分:2)

更好的方法可能是:

class Foo
{

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

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

即不存储指向vector元素的指针,而是存储它的索引。这样它就可以免于复制/调整后备存储的向量。

答案 3 :(得分:1)

移动构造的情况保证将缓冲区从一个容器移动到另一个容器,因此从新创建的对象的角度来看,操作正常。

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

虽然通常在移动之后不会使用你的对象(假设被rvalue-reference绑定它必须是一个rvalue),事实是你可以移出一个lvalue通过强制转换或使用std::move(基本上是强制转换),在这种情况下代码可能实际上会尝试使用您的对象。