Предотвращение создания элементов по умолчанию в стандартных контейнерах - PullRequest
10 голосов
/ 28 августа 2011

Я заинтересован в создании контейнера uninitialized_vector, который будет семантически идентичен std::vector с оговоркой, что новые элементы, которые в противном случае были бы созданы с помощью конструктора без аргументов, будут созданы без инициализации.В первую очередь я заинтересован в том, чтобы избежать инициализации POD равным 0. Насколько я могу судить, нет никакого способа добиться этого, комбинируя std::vector со специальным типом распределителя.

IЯ хотел бы построить мой контейнер в том же ключе, что и std::stack, который адаптирует предоставленный пользователем контейнер (в моем случае, std::vector).Другими словами, я бы хотел избежать переопределения всего std::vector и вместо этого предоставить «фасад» вокруг него.

Существует ли простой способ управления конструкцией по умолчанию из «снаружи» std::vector?


Вот решение, к которому я пришел, которое вдохновило Керрека:

#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
#include <cassert>

// uninitialized_allocator adapts a given base allocator
// uninitialized_allocator's behavior is equivalent to the base
// except for its no-argument construct function, which is a no-op
template<typename T, typename BaseAllocator = std::allocator<T>>
  struct uninitialized_allocator
    : BaseAllocator::template rebind<T>::other
{
  typedef typename BaseAllocator::template rebind<T>::other super_t;

  template<typename U>
    struct rebind
  {
    typedef uninitialized_allocator<U, BaseAllocator> other;
  };

  // XXX for testing purposes
  typename super_t::pointer allocate(typename super_t::size_type n)
  {
    auto result = super_t::allocate(n);

    // fill result with 13 so we can check afterwards that
    // the result was not default-constructed
    std::fill(result, result + n, 13);
    return result;
  }

  // catch default-construction
  void construct(T *p)
  {
    // no-op
  }

  // forward everything else with at least one argument to the base
  template<typename Arg1, typename... Args>
    void construct(T* p, Arg1 &&arg1, Args&&... args)
  {
    super_t::construct(p, std::forward<Arg1>(arg1), std::forward<Args>(args)...);
  }
};

namespace std
{

// XXX specialize allocator_traits
//     this shouldn't be necessary, but clang++ 2.7 + libc++ has trouble
//     recognizing that uninitialized_allocator<T> has a well-formed
//     construct function
template<typename T>
  struct allocator_traits<uninitialized_allocator<T> >
    : std::allocator_traits<std::allocator<T>>
{
  typedef uninitialized_allocator<T> allocator_type;

  // for testing purposes, forward allocate through
  static typename allocator_type::pointer allocate(allocator_type &a, typename allocator_type::size_type n)
  {
    return a.allocate(n);
  }

  template<typename... Args>
    static void construct(allocator_type &a, T* ptr, Args&&... args)
  {
    a.construct(ptr, std::forward<Args>(args)...);
  };
};

}

// uninitialized_vector is implemented by adapting an allocator and
// inheriting from std::vector
// a template alias would be another possiblity

// XXX does not compile with clang++ 2.9
//template<typename T, typename BaseAllocator>
//using uninitialized_vector = std::vector<T, uninitialized_allocator<T,BaseAllocator>>;

template<typename T, typename BaseAllocator = std::allocator<T>>
  struct uninitialized_vector
    : std::vector<T, uninitialized_allocator<T,BaseAllocator>>
{};

int main()
{
  uninitialized_vector<int> vec;
  vec.resize(10);

  // everything should be 13
  assert(std::count(vec.begin(), vec.end(), 13) == vec.size());

  // copy construction should be preserved
  vec.push_back(7);
  assert(7 == vec.back());

  return 0;
}

Это решение будет работать в зависимости от того, насколько близко компилятор конкретного поставщика и STL's std::vectorреализация соответствует c ++ 11.

Ответы [ 3 ]

6 голосов
/ 28 августа 2011

Вместо использования оболочки вокруг контейнера рассмотрите возможность использования оболочки вокруг элемента:

template <typename T>
struct uninitialized
{
    uninitialized() { }
    T value;
};
5 голосов
/ 28 августа 2011

Я думаю, что проблема сводится к типу инициализации, которую контейнер выполняет для элементов. Для сравнения:

T * p1 = new T;   // default-initalization
T * p2 = new T(); // value-initialization

Проблема со стандартными контейнерами заключается в том, что они принимают аргумент по умолчанию для инициализированного значения, как в resize(size_t, T = T()). Это означает, что не существует элегантного способа избежать инициализации значения или копирования. (Аналогично для конструктора.)

Даже использование стандартных распределителей не работает, потому что их центральная функция construct() принимает аргумент, который инициализируется значением. Вам скорее всего понадобится construct(), который использует инициализацию по умолчанию:

template <typename T>
void definit_construct(void * addr)
{
  new (addr) T;  // default-initialization
}

Такая вещь больше не будет соответствовать стандартному распределителю, но вы можете построить свой собственный контейнер на основе этой идеи.

1 голос
/ 28 августа 2011

Я не верю, что это возможно путем переноса вектора (который работает с каждым типом), если только вы не измените размер вектора на при каждой операции добавления и удаления .

Если бы вы моглиотказаться от упаковки контейнеров STL, вы можете сделать это, сохранив массив char в куче и используя размещение new для каждого из объектов, которые вы хотите построить.Таким образом, вы можете точно контролировать, когда по одному вызывались конструкторы и деструкторы объектов.

...