简体   繁体   中英

How to let a `std::vector<int32_t>` take memory from a `std::vector<uint32_t>&&`?

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

Suppose we have raster<uint32_t> r such that rvsize() is in the millions, and a guarantee that all its elements are within int32_t 's range. Is it possible for raster<int32_t>::raster(raster<uint32_t>&& other) to avoid copying the memory backing other.v ?

Or should I just do something like *reinterpret_cast<raster<int32_t>*>(&r) instead of calling that constructor?

There is no legal way to do this in C++; you can only move buffers from a std::vector to another std::vector of the exact same type.

There are a variety of ways you can hack this. The most illegal and evil would be

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

or something similar.

A less evil way would be to forget it is a std::vector at all.

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 : this type erases a buffer of 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 ) )
  {}
};

note that my buffer has a memory allocation in it; compared to millions of elements that is cheap. It is also move-only.

The memory allocation can be eliminated through careful use of the small buffer optimization.

I'd leave it move-only (who wants to accidentally copy a million elements?) and maybe write

buffer clone() const;

which creates a new buffer with the same contents.

Note that instead of a const buffer<int> you should use a buffer<const int> under the above design. You can change that by duplicating the begin() const methods to have const and non-const versions.

This solution relies on your belief that reinterpreting a buffer of int32_t s as a buffer of uint32_t s (or vice versa) doesn't mess with anything. Your compiler may provide this guarantee, but the C++ standard does not .

The problem is that the implementation of the vector template itself may be specialised for the type. For some weird whacky reason we don't understand today, the top-level vector might need an extra member not provided in vector, so simply reinterpret casting will not work safely.

Another evil approach might be to look at the Allocator of your two vectors. If this was

  • a custom allocator you had written and both were a derived class from vector
  • and you wrote an overload in the class for swap and =&&
  • within which you created wrapped tmp vectors to swap with
  • and detected that the Allocator was being called within those temporary objects constructors/destructors, and on the same thread
  • to release and reallocate the same sized array

Then, perhaps you could legitimately pass the memory buffer between them without resetting the content.

But that is a lot of fragile work!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM