2012-04-14 3 views
1

Я с удивлением обнаружил вектор :: erase move elements при вызове erase. Я думал, что он заменит последний элемент элементом «быть удаленным» и уменьшит размер на единицу. Моя первая реакция была: «Давайте продолжим std :: vector и over-ride erase()». Но я нашел во многих потоках, таких как «Is there any real risk to deriving from the C++ STL containers?», что может вызвать утечку памяти. Но я не добавляю ни одного нового элемента данных в вектор. Таким образом, нет дополнительной памяти для освобождения. Есть ли риск?нужен std :: vector with O (1) erase

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

+7

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

+1

Не нарушает ли принцип ООП? функции-члены должны иметь ИСКЛЮЧИТЕЛЬНЫЕ права на изменение данных-членов. Конечно, я согласен с тем, что принципы ООП не «отправлены богом», но я ценю необходимость инкапсуляции, чтобы лучше рассуждать о моих программах. –

+2

@AbhishekAnand: Наоборот, чем больше функций-членов у вас есть, тем хуже ваше инкапсуляция. – GManNickG

ответ

4

Нежный выпуск. Первая рекомендация, которую вы нарушаете: «Inheritance is not for code reuse». Второе: «Не наследовать от стандартных библиотечных контейнеров».

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

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


Edit: Что я имею в виду 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.

+0

Немного меньше кода? Вы не нуждаетесь в каких-либо из этих «используемых» заявлений с композицией. – leftaroundabout

+2

@leftaroundabout С композицией вам нужно будет реализовать целую кучу функций пересылки вместо одного оператора 'using'. – Praetorian

+0

@leftaroundabout: Нет, вам не понадобится инструкция 'using' в первую очередь. Вместо этого вам потребуется реализовать оболочку для каждого отдельного оператора 'using'. Упаковщики намного дольше записывают и более склонны ломаться при обслуживании (возможно, даже тихо).Однако я даю вам, что интерфейс 'vector' не будет существенно изменяться между стандартами. – bitmask

3

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

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

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

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

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

+0

все элементы данных находятся в базовом классе. Я НЕ ДОБАВЛЯЮ ЛЮБОЙ НОВЫЙ ЧЛЕН В better_vector. Таким образом, деструктор базового класса должен иметь возможность выполнять всю очистку. Почему произошла утечка памяти? –

+5

@AbhishekAnand: Это не имеет значения. Вы все еще вызываете неопределенное поведение, если вы пропустите деструктор производного класса. Вероятно, это работает с любым компилятором, который вы тестируете, но это по-прежнему плохая практика. – bitmask

11

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

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

Всей кредитного Сет Карнеги, хотя. Первоначально я использовал «std :: swap».

+5

Лучшей реализацией может быть 'v [i] = std :: move (v.back()); v.pop_back(); ', но +1 –

+0

Просто изменил его, чтобы использовать std :: swap – cdiggins

+0

' swap', вероятно, будет хуже, чем ваш первый, поскольку с 'swap' вы создаете временное и выполняете два назначения вместо no temps и только одно задание. –

2

Я думал, что он заменит последний элемент элементом «to-be-deleted» и уменьшит размер на единицу.

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

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

+0

Мне тоже нужен случайный доступ. –

Смежные вопросы