Как позволить `std :: vector <int32_t>` забирать память у `std :: vector <uint32_t> &&`? - PullRequest
0 голосов
/ 04 июля 2018
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 голосов
/ 05 июля 2018

Проблема в том, что реализация самого векторного шаблона может быть специализированной для данного типа. По какой-то странной причудливой причине, которую мы сегодня не понимаем, для вектора верхнего уровня может потребоваться дополнительный член, не предоставленный в векторе, поэтому простое переосмысление приведения не будет работать безопасно.

Другим злым подходом может быть просмотр распределителя ваших двух векторов. Если бы это было

  • пользовательский распределитель, который вы написали, и оба были производными от вектора
  • и вы написали перегрузку в классе для swap и = &&
  • , внутри которого вы создали обернутые векторы tmp для замены на
  • и обнаружил, что Allocator вызывался в конструкторах / деструкторах временных объектов и в том же потоке
  • для освобождения и перераспределения массива того же размера

Тогда, возможно, вы могли бы законно передать буфер памяти между ними без сброса содержимого.

Но это очень хрупкая работа!

0 голосов
/ 04 июля 2018

Нет законного способа сделать это в 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;
};

живой пример : этот тип стирает буфер 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, чтобы иметь постоянную и неконстантную версии.

Это решение основано на вашей уверенности, что реинтерпретация буфера int32_t s как буфера uint32_t s (или наоборот) ни с чем не повлияет. Ваш компилятор может предоставить эту гарантию, но стандарт C ++ не .

...