YAKOVM
YAKOVM

Reputation: 10153

vector vs. list from stl - remove method

std::list has a remove method, while the std::vector doesn't. What is the reason for that?

Upvotes: 0

Views: 1473

Answers (7)

UpAndAdam
UpAndAdam

Reputation: 5477

It's all about efficiency AND reference/pointer/iterator validity. list items can be removed without disturbing any other pointers and iterators. This is not true for a vector and other containers in all but the most trivial cases. Nothing prevents use the external strategy, but you have a superior options.. That said this fellow said it better than I could on a duplicate question

From another poster on a duplicate question:

The question is not why std::vector does not offer the operation, but rather why does std::list offer it. The design of the STL is focused on the separation of the containers and the algorithms by means of iterators, and in all cases where an algorithm can be implemented efficiently in terms of iterators, that is the option.

There are, however, cases where there are specific operations that can be implemented much more efficiently with knowledge of the container. That is the case of removing elements from a container. The cost of using the remove-erase idiom is linear in the size of the container (which cannot be reduced much), but that hides the fact that in the worst case all but one of those operations are copies of the objects (the only element that matches is the first), and those copies can represent quite a big hidden cost.

By implementing the operation as a method in std::list the complexity of the operation will still be linear, but the associated cost for each one of the elements removed is very low, a couple of pointer copies and releasing of a node in memory. At the same time, the implementation as part of the list can offer stronger guarantees: pointers, references and iterators to elements that are not erased do not become invalidated in the operation.

Another example of an algorithm that is implemented in the specific container is std::list::sort, that uses mergesort that is less efficient than std::sort but does not require random-access iterators.

So basically, algorithms are implemented as free functions with iterators unless there is a strong reason to provide a particular implementation in a concrete container.

Upvotes: 1

lethal-guitar
lethal-guitar

Reputation: 4519

Consider the implementation details of both containers. A vector has to provide a continuous memory block for storage. In order to remove an element at index n != N (with N being the vector's length), all elements from n+1 to N-1 need to be moved. The various functions in the <algorithm> header implement that behavior, like std::remove or std::remove_if. The advantage of these being free-standing functions is that they can work for any type that offers the needed iterators.

A list on the other hand, is implemented as a linked list structure, so:

  • It's fast to remove an element from anywhere
  • It's impossible to do it as efficiently using iterators (since the internal structure has to be known and manipulated).

Upvotes: 2

AnT stands with Russia
AnT stands with Russia

Reputation: 320719

std::list<>::remove is a physical removal method, which can be implemented by physically destroying list elements that satisfy certain criteria (by physical destruction I mean the end of element's storage duration). Physical removal is only applicable to lists. It cannot be applied to arrays, like std::vector<>. It simply is not possible to physically end storage duration of an individual element of an array. Arrays can only be created and destroyed as a whole. This is why std::vector<> does not have anything similar to std::list<>::remove.

The universal removal method applicable to all modifiable sequences is what one might call logical removal: the target elements are "removed" from the sequence by overwriting their values with values of elements located further down in the sequence. I.e. the sequence is shifted and compacted by copying the persistent data "to the left". Such logical removal is implemented by freestanding functions, like std::remove. Such functions are applicable in equal degree to both std::vector<> and std::list<>.

In cases where the approach based on immediate physical removal of specific elements applies, it will work more efficiently than the generic approach I referred above as logical removal. That is why it was worth providing it specifically for std::list<>.

Upvotes: 4

Zac Howland
Zac Howland

Reputation: 15870

std::list::remove removes all items in a list that match the provided value.

std::list<int> myList;
// fill it with numbers
myList.remove(10); // physically removes all instances of 10 from the list

It has a similar function, std::list::remove_if, which allows you to specify some other predicate.

std::list::remove (which physically removes the elements) is required to be a member function as it needs to know about the memory structure (that is, it must update the previous and next pointers for each item that needs to be updated, and remove the items), and the entire function is done in linear time (a single iteration of the list can remove all of the requested elements without invalidating any of the iterators pointing to items that remain).

You cannot physically remove a single element from a std::vector. You either reallocate the entire vector, or you move every element after the removed items and adjust the size member. The "cleanest" implementation of that set of operations would be to do

// within some instance of vector
void vector::remove(const T& t)
{
    erase(std::remove(t), end());
}

Which would require std::vector to depend on <algorithm> (something that is currently not required).

As the "sorting" is needed to remove the items without multiple allocations and copies being required. (You do not need to sort a list to physically remove elements).

Contrary to what others are saying, it has nothing to do with speed. It has to do with the algorithm needing to know how the data is stored in memory.

As a side note: This is also a similar reason why std::remove (and friends) do not actually remove the items from the container they operate on; they just move all the ones that are not going to be removed to the "front" of the container. Without the knowledge of how to actually remove an object from a container, the generic algorithm cannot actually do the removing.

Upvotes: 3

dnk
dnk

Reputation: 661

To remove an element from a container you have to find it first. There's a big difference between sorted and unsorted vectors, so in general, it's not possible to implement an efficient remove method for the vector.

Upvotes: 0

David
David

Reputation: 2272

std::list is designed to work like a linked list. That is, it is designed (you might say optimized) for constant time insertion and removal ... but access is relatively slow (as it typically requires traversing the list).

std::vector is designed for constant-time access, like an array. So it is optimized for random access ... but insertion and removal are really only supposed to be done at the "tail" or "end", elsewhere they're typically going to be much slower.

Different data structures with different purposes ... therefore different operations.

Upvotes: 0

rabensky
rabensky

Reputation: 2934

In general in STL the logic is "if it can be done efficiently - then it's a class member. If it's inefficient - then it's an outside function"

This way they make the distinction between "correct" (i.e. "efficient") use of classes vs. "incorrect" (inefficient) use.

For example, random access iterators have a += operator, while other iterators use the std::advance function.

And in this case - removing elements from an std::list is very efficient as you don't need to move the remaining values like you do in std::vector

Upvotes: 1

Related Questions