Существует ли контейнер C ++ с разумным произвольным доступом, который никогда не вызывает конструктор копирования типа элемента? - PullRequest
4 голосов
/ 19 августа 2010

Мне нужен контейнер, который реализует следующий API (и больше ничего не должен реализовывать):

class C<T> {
  C();

  T& operator[](int); // must have reasonably sane time constant

    // expand the container by default constructing elements in place.
  void resize(int); // only way anything is added.
  void clear();

  C<T>::iterator begin();
  C<T>::iterator end();
}

и может использоваться на:

class I {
 public:
  I();
 private:  // copy and assignment explicate disallowed
  I(I&);
  I& operator=(I&);
}

Доза такого зверя существует?

vector<T> не делает этого (изменяет размеры ходов), и я не уверен, насколько быстро deque<T>.


Меня не волнует распределение

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


Похоже, у меня есть ответ: у STL его нет. Но теперь мне интересно Почему бы и нет?

Ответы [ 7 ]

5 голосов
/ 19 августа 2010

Я почти уверен, что ответ здесь довольно решительный "Нет" . По вашему определению, resize() должен выделить новое хранилище и инициализироваться конструктором по умолчанию, если я правильно читаю. Тогда вы будете манипулировать объектами, индексируя в коллекцию и манипулируя ссылкой, а не «вставляя» в коллекцию. В противном случае вам понадобится конструктор копирования и оператор присваивания. Все контейнеры в стандартной библиотеке имеют это требование.

Возможно, вы захотите использовать что-то вроде boost::ptr_vector<T>. Поскольку вы вставляете указатели, вам не нужно беспокоиться о копировании. Это потребует от вас динамического выделения всех ваших объектов.

5 голосов
/ 19 августа 2010

Вы можете использовать контейнер указателей, например std::vector<T*>, если элементы не могут быть скопированы, а их память управляется вручную в другом месте.

Если вектор должен владеть элементами, может подойти что-то вроде std::vector< std::shared_ptr<T> >.

А также есть библиотека Boost Pointer Container , которая предоставляет контейнеры для безопасной обработки указателей.

4 голосов
/ 19 августа 2010

Использование deque: производительность в порядке.

Стандарт гласит: «deque - это выбранная структура данных, когда большинство вставок и удалений происходит в начале или в конце последовательности» (23.1.1). В вашем случае все вставки и удаления выполняются в конце, удовлетворяя критерию для использования deque.

http://www.gotw.ca/gotw/054.htm содержит некоторые подсказки о том, как вы можете измерить производительность, хотя, по-видимому, вы имеете в виду конкретный вариант использования, поэтому вам следует это измерить.

Редактировать: ОК, если ваше возражение против deque на самом деле не "Я не уверен, насколько быстрым является deque", но "тип элемента не может быть элементом в стандартном контейнере", тогда мы может исключить любой стандартный контейнер. Нет, такого зверя не существует. deque "никогда не копирует элементы", но создает и копирует их из других объектов.

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

template <typename T>
struct C {
    vector<shared_array<T> > blocks;
    vector<T*> elements; // lazy, to avoid needing deque-style iterators through the blocks.
    T &operator[](size_t idx) { return *elements[idx]; }
    void resize(size_t n) {
        if (n <= elements.size()) { /* exercise for the reader */ }
        else {
            boost::shared_array<T> newblock(new T[elements.size() - n]);
            blocks.push_back(newblock);
            size_t old = elements.size();
            // currently we "leak" newblock on an exception: see below
            elements.resize(n);
            for (int i = old; j < n; ++i) {
                elements[i] = &newblock[i - old];
            }
    }
    void clear() {
        blocks.clear();
        elements.clear();
    }
};

Когда вы добавляете больше функций и операторов, он приближается к deque, но избегает всего, что требует копирования типа T.

Редактировать: если подумать, мое «упражнение для читателя» не может быть выполнено совершенно правильно в тех случаях, когда кто-то делает resize(10); resize(20); resize(15);. Вы не можете наполовину удалить массив. Поэтому, если вы хотите правильно воспроизвести семантику контейнера resize(), немедленно уничтожив лишние элементы, вам придется выделить элементы индивидуально (или познакомиться с новым размещением):

template <typename T>
struct C {
    deque<shared_ptr<T> > elements; // or boost::ptr_deque, or a vector.
    T &operator[](size_t idx) { return *elements[idx]; }
    void resize(size_t n) {
        size_t oldsize = elements.size();
        elements.resize(n);
        if (n > oldsize) {
            try {
                for (size_t i = oldsize; i < n; ++i) {
                    elements[i] = shared_ptr<T>(new T());
                }
            } catch(...) {
                // closest we can get to strong exception guarantee, since
                // by definition we can't do anything copy-and-swap-like
                elements.resize(oldsize);
                throw;
            }
        }
    }
    void clear() {
        elements.clear();
    }
};

Хороший код, не особо увлекающийся шаблонами доступа к памяти (но тогда я не уверен, является ли производительность проблемой или нет, так как вы беспокоились о скорости deque.)

3 голосов
/ 19 августа 2010

Как вы обнаружили, все стандартные контейнеры несовместимы с вашими требованиями.Если мы сможем сделать пару дополнительных предположений, не составит труда написать свой собственный контейнер.

  • Контейнер всегда будет расти - resize всегда будет вызываться с большим числомранее, никогда не меньше.
  • Это нормально для resize, чтобы сделать контейнер больше, чем было запрошено;допустимо создание некоторого числа неиспользуемых объектов в конце контейнера.

Вот начало.Я оставляю вам многие детали.

class C<T> { 
  C();
  ~C() { clear(); }

  T& operator[](int i) // must have reasonably sane time constant 
  {
      return blocks[i / block_size][i % block_size];
  }

    // expand the container by default constructing elements in place. 
  void resize(int n) // only way anything is added. 
  {
      for (int i = (current_size/block_size)+1; i <= n/block_size;  ++i)
      {
          blocks.push_back(new T[block_size]);
      }
      current_size = n;
  }

  void clear()
  {
      for (vector<T*>::iterator i = blocks.begin();  i != blocks.end();  ++i)
          delete[] *i;
      current_size = 0;
  }

  C<T>::iterator begin(); 
  C<T>::iterator end(); 
private:
  vector<T*> blocks;
  int current_size;
  const int block_size = 1024; // choose a size appropriate to T
} 

PS Если кто-нибудь спросит вас, почему вы хотите это сделать, скажите им, что вам нужен массив std::auto_ptr.Это должно быть хорошо для смеха.

2 голосов
/ 19 августа 2010

Все стандартные контейнеры требуют копирования элементов. По крайней мере, потому что push_back и insert копируют переданный им элемент. Я не думаю, что вы можете обойтись без std :: deque, потому что даже его метод resize принимает параметр для копирования для заполнения элементов.

Чтобы использовать полностью не копируемый класс в стандартных контейнерах, вы должны хранить указатели на эти объекты. Иногда это может быть обременительным, но использование shared_ptr или различных контейнеров повышающих указателей может облегчить задачу.

Если вам не нравится ни одно из этих решений, просмотрите остальные варианты. Может быть, там есть что-то еще подходящее. Возможно навязчивые контейнеры ?

В противном случае, если вы не считаете, что что-то соответствует вашим потребностям, вы всегда можете попытаться свернуть свой собственный контейнер, который выполняет то, что вы хотите. (Или сделайте больше поиска, чтобы увидеть, делал ли кто-либо еще такую ​​вещь.)

1 голос
/ 19 августа 2010

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

Практически каждый контейнер будет выделять память, если вы ее измените!Конечно, вы можете изменить емкость заранее, позвонив по номеру vector::reserve.Емкость - это количество физических элементов в памяти, а размер - это то, сколько вы активно используете.

Очевидно, что выделение будет, если вы превысите свою емкость.

0 голосов
/ 20 августа 2010

Посмотрите на ::boost::array.Он не позволяет изменять размер контейнера после его создания, но никогда ничего не копирует.

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

Кроме того, наличие системы страниц обязательно означает, что at не будеттак же быстро, как это было бы для ::std::vector и ::boost::array с их непрерывным расположением памяти, даже если это все еще может быть довольно быстро.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...