如何让`std :: vector <int32_t>`从`std :: vector <uint32_t> &&`获取内存?

时间:2018-07-04 18:12:09

标签: c++ c++14 stdvector move-semantics reinterpret-cast

template<typename T>
struct raster {
    std::vector<T> v;

    template<typename U, typename = std::enable_if_t<sizeof(T) == sizeof(U)>>
    raster(raster<U>&& other){
        // What code goes here?
    }
}

假设我们拥有raster<uint32_t> r,以使r.v.size()达到数百万,并保证其所有元素都在int32_t的范围内。 raster<int32_t>::raster(raster<uint32_t>&& other)是否有可能避免复制内存备份other.v

还是我应该做类似*reinterpret_cast<raster<int32_t>*>(&r)的事情而不是调用该构造函数?

2 个答案:

答案 0 :(得分:5)

在C ++中没有合法的方法可以做到这一点;您只能将缓冲区从std::vector移到完全相同类型的另一个std::vector

您可以通过多种方式来破解此漏洞。最非法,最邪恶的是

std::vector<uint32_t> evil_steal_memory( std::vector<int32_t>&& in ) {
  return reinterpret_cast< std::vector<uint32_t>&& >(in);
}

或类似的东西。

一种不太邪恶的方法是忘记它根本就是std::vector

template<class T>
struct buffer {
  template<class A>
  buffer( std::vector<T,A> vec ):
    m_begin( vec.data() ),
    m_end( m_begin + vec.size() )
  {
    m_state = std::unique_ptr<void, void(*)(void*)>(
      new std::vector<T,A>( std::move(vec) ),
      [](void* ptr){
        delete static_cast<std::vector<T,A>*>(ptr);
      }
    );
  }
  buffer(buffer&&)=default;
  buffer& operator=(buffer&&)=default;
  ~buffer() = default;
  T* begin() const { return m_begin; }
  T* end() const { return m_end; }
  std::size_t size() const { return begin()==end(); }
  bool empty() const { return size()==0; }
  T* data() const { return m_begin; }
  T& operator[](std::size_t i) const {
    return data()[i];
  }
  explicit operator bool() const { return (bool)m_state; }

  template<class U>
  using is_buffer_compatible = std::integral_constant<bool,
    sizeof(U)==sizeof(T)
    && alignof(U)==alignof(T)
    && !std::is_pointer<T>{}
  >;
  template<class U,
    std::enable_if_t< is_buffer_compatible<U>{}, bool > = true
  >
  buffer reinterpret( buffer<U> o ) {
    return {std::move(o.m_state), reinterpret_cast<T*>(o.m_begin()),reinterpret_cast<T*>(o.m_end())};
  }
private:
  buffer(std::unique_ptr<void, void(*)(void*)> state, T* b, T* e):
    m_state(std::move(state)),
    m_begin(begin),
    m_end(end)
  {}
  std::unique_ptr<void, void(*)(void*)> m_state;
  T* m_begin = 0;
  T* m_end = 0;
};

live example:此类型会擦除T的缓冲区。

template<class T>
struct raster {
  buffer<T> v;

  template<typename U, typename = std::enable_if_t<sizeof(T) == sizeof(U)>>
  raster(raster<U>&& other):
    v( buffer<T>::reinterpret( std::move(other).v ) )
  {}
};

请注意,我的buffer中有一个内存分配;相较于便宜的数百万个元素。它也是仅移动的。

可以通过谨慎使用小缓冲区优化来消除内存分配。

我将其保留为仅移动(谁想意外复制一百万个元素?)并可能写

buffer clone() const;

这将创建一个具有相同内容的新缓冲区。

请注意,在上述设计下,您应该使用const buffer<int>而不是buffer<const int>。您可以通过复制begin() const方法以具有const版本和非const版本来更改它。

此解决方案基于您的信念,即将int32_t s的缓冲区重新解释为uint32_t s的缓冲区(反之亦然)不会有任何问题。您的编译器可以提供此保证,但C ++标准

答案 1 :(得分:0)

问题在于向量模板本身的实现可能专门针对该类型。出于某些我们今天不了解的怪异怪异原因,顶层向量可能需要向量中未提供的额外成员,因此仅重新解释强制转换将无法安全地工作。

另一个邪恶的方法可能是查看两个向量的分配器。 如果是

  • 您编写的自定义分配器,它们都是从vector派生的类
  • 您在类中为swap和= &&
  • 写了一个重载
  • 在其中创建了可与之交换的包装的tmp矢量
  • 并检测到在这些临时对象的构造函数/析构函数内以及同一线程上正在调用分配器
  • 释放并重新分配相同大小的数组

然后,也许您可​​以合法地在它们之间传递内存缓冲区,而无需重置内容。

但这是很多脆弱的工作!