нужен std :: vector с удалением O (1)

Я был удивлен, обнаружив, что элементы перемещения vector :: erase при вызове erase. Я думал, что он заменит последний элемент «подлежащим удалению» элементом и уменьшит размер на единицу. Моя первая реакция была: «Давайте расширим std :: vector и заменим erase ()». Но я нашел во многих темах, например, «Есть ли реальный риск наследования контейнеров C ++ STL? ", что это может вызвать утечку памяти. Но я не добавляю в вектор никаких новых данных. Таким образом, не нужно освобождать дополнительную память. Есть ли еще риск?

Некоторые предлагают предпочесть композицию наследованию. Я не могу понять этот совет в этом контексте. Зачем мне тратить свое время на «механическую» задачу обертывания каждой функции замечательного в остальном класса std :: vector? Наследование действительно имеет наибольший смысл для этой задачи - или я что-то упускаю?


person Abhishek Anand    schedule 14.04.2012    source источник
comment
Почему бы просто не написать бесплатную функцию, которая делает то, что вы хотите?   -  person Seth Carnegie    schedule 15.04.2012
comment
Разве это не нарушает принципы ООП? функции-члены должны иметь ИСКЛЮЧИТЕЛЬНЫЕ права на изменение данных-членов. Конечно, я согласен с тем, что принципы ООП не даны богом, но я ценю необходимость инкапсуляции, чтобы лучше разбираться в моих программах.   -  person Abhishek Anand    schedule 15.04.2012
comment
@AbhishekAnand: Напротив, чем больше у вас функций-членов, тем хуже ваша инкапсуляция.   -  person GManNickG    schedule 15.04.2012
comment
@Abhishek Нет, это определенно не нарушает принципы ООП. Вы бы просто использовали интерфейс; вы бы не получили доступ к внутренним данным или не сделали бы их дружественной функцией (что нарушило бы принципы ООП). Также обратите внимание на заголовок algorithm: есть десятки функций, которые изменяют контейнеры. Если вы сделаете это так, как думаете, тогда вы столкнетесь с проблемой N * M.   -  person Seth Carnegie    schedule 15.04.2012
comment
@SethCarnegie, спасибо ... Кстати, в чем проблема N * M?   -  person Abhishek Anand    schedule 15.04.2012
comment
@SethCarnegie: Чтобы быть разборчивым, но заголовок algorithm не содержит только функции, которые изменяют содержимое контейнеров по диапазонам итераторов.   -  person dalle    schedule 15.04.2012
comment
@AbhishekAnand проблема N M - это то, что происходит, когда у вас есть N контейнеров и M алгоритмов (ужасно большое количество), у вас должно быть N M реализаций. Решение состоит в том, чтобы сделать их шаблонными функциями, не являющимися членами, и в этом случае у вас есть только реализации M (что является оптимальным).   -  person Seth Carnegie    schedule 15.04.2012
comment
@dalle no, например sort изменяет не содержимое (фактические элементы), а порядок элементов.   -  person Seth Carnegie    schedule 15.04.2012
comment
@SethCarnegie: разница в том, что функции в algorithm не могут добавлять или удалять элементы в коллекции, поскольку функции в algorithm работают с диапазонами итераторов. Чтобы добавить / удалить элементы в коллекции, вам необходимо использовать правильную функцию-член или вспомогательные классы (например, back_insert_iterator, front_insert_iterator и insert_iterator) в заголовке iterator.   -  person dalle    schedule 15.04.2012


Ответы (4)


Деликатный вопрос. Первое правило, которое вы нарушаете: «Наследование не для кода повторно использовать ". Второй: «Не наследовать от стандартных библиотечных контейнеров».

Но: Если вы можете гарантировать, что никто никогда не будет использовать ваш unordered_vector<T> как vector<T>, вы хороши. Однако, если кто-то это сделает, результаты могут быть неопределенными и / или ужасными, независимо от того, сколько у вас участников (может показаться, что это работает отлично, но, тем не менее, будет неопределенным поведением!).

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


Изменить: что я имею в виду с заявлениями using, так это:

class Base {
  public:
    void dosmth();
};

class Derived : private Base {
  public:
    using Base::dosmth;
};

class Composed {
  private:
    Base base;
  public:
    void dosmth() {return base.dosmth(); }
};

Вы можете сделать это со всеми функциями-членами std::vector. Как видите, Derived - это значительно меньше кода, чем Composed.

person bitmask    schedule 14.04.2012
comment
Чуть меньше кода? Вам не понадобится ни один из этих using операторов с композицией. - person leftaroundabout; 15.04.2012
comment
@leftaroundabout При использовании композиции вам потребуется реализовать целый набор функций пересылки вместо одного оператора using. - person Praetorian; 15.04.2012
comment
@leftaroundabout: Нет, вам вообще не понадобится инструкция using. Вместо этого вам нужно будет реализовать оболочку для каждого оператора using. Обертки намного дольше записываются и более склонны к прерыванию при обслуживании (возможно, даже незаметно). Однако я сообщаю вам, что интерфейс vector не будет существенно меняться между стандартами. - person bitmask; 15.04.2012
comment
Ах, я не знал, что using в разделе public фактически делает унаследованные функции общедоступными. Что-то снова узнал! - person leftaroundabout; 15.04.2012
comment
Как вы думаете, не будет ТАКОГО количества using, особенно по сравнению с количеством typedef, которые вам все равно понадобятся, если вы хотите быть совместимыми с STL. Однако вам нужно будет подумать о том, какие концепции STL вы хотите реализовать в любом случае (подсказка: не последовательность), и вам, возможно, придется реализовать некоторые оболочки операторов, если ваша текущая или будущая реализация операторов переопределения векторов использует бесплатные функции. - person BatchyX; 15.04.2012
comment
@leftaroundabout: Я отредактировал сообщение, чтобы прояснить этот момент. - person bitmask; 15.04.2012

Почему бы просто не написать отдельную функцию, которая делает то, что вы хотите:

template<typename T>
void fast_erase(std::vector<T>& v, size_t i)
{
   v[i] = std::move(v.back());
   v.pop_back(); 
}

Но все заслуги Сета Карнеги. Изначально я использовал "std :: swap".

person cdiggins    schedule 14.04.2012
comment
Лучшая реализация могла бы быть v[i] = std::move(v.back()); v.pop_back();, но +1 - person Seth Carnegie; 15.04.2012
comment
Просто изменил его, чтобы использовать std :: swap - person cdiggins; 15.04.2012
comment
swap, вероятно, будет хуже, чем ваш первый, так как с swap вы создаете временный и выполняете два задания вместо без временных и только одно задание. - person Seth Carnegie; 15.04.2012
comment
Я бы заменил v.erase(size() - 1) на v.pop_back() - person Blastfurnace; 15.04.2012
comment
Такой подход проблематичен, потому что у пользователей вашего контейнера может возникнуть блестящая идея - по-прежнему вызывать std::vector<T>::erase. - person bitmask; 15.04.2012
comment
@bitmask при таком подходе это не ваш контейнер; это std::vector, а вы просто пишете для него очень маленькую служебную функцию. Это определенно лучший подход. - person Seth Carnegie; 15.04.2012
comment
@bitmmask Сет прав. Стирание - это преднамеренная операция по сохранению порядка. - person cdiggins; 15.04.2012
comment
@SethCarnegie: У меня сложилось впечатление, что OP хотел предоставить альтернативу unordered_vector с быстрой erase реализацией. - person bitmask; 15.04.2012
comment
@bitmask он d (id | oes), но мы пытаемся убедить его не делать этого. - person Seth Carnegie; 15.04.2012
comment
Я бы тоже не стал называть эту функцию fast_erase, а что-то вроде unordered_erase. - person leftaroundabout; 15.04.2012

Риск наследования представлен в следующем примере:

std::vector<something> *v = new better_vector<something>();
delete v;

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

better_vector<something> *v = new better_vector<something>();
delete v;

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

person Dani    schedule 14.04.2012
comment
все члены данных находятся в базовом классе. Я НЕ ДОБАВЛЯЮ НОВОГО ЧЛЕНА В better_vector. Таким образом, деструктор базового класса должен уметь выполнять всю очистку. Почему может произойти утечка памяти? - person Abhishek Anand; 15.04.2012
comment
@AbhishekAnand: Это не имеет значения. Вы по-прежнему вызываете неопределенное поведение, если пропускаете деструктор производного класса. Вероятно, он работает с любым компилятором, который вы тестируете, но это все еще плохая практика. - person bitmask; 15.04.2012

Я думал, что он заменит последний элемент «подлежащим удалению» элементом и уменьшит размер на единицу.

vector :: erase поддерживает порядок элементов при перемещении последнего элемента в стертый элемент и не уменьшает размер на единицу. Поскольку вектор реализует массив, нет способа O (1) поддерживать порядок элементов и одновременно стирать (если вы не удалите последний элемент).

Если поддержание порядка элементов не важно, ваше решение в порядке, в противном случае вам лучше использовать другие контейнеры (например, список, который реализует двусвязный список).

person invisal    schedule 14.04.2012