Why Doesn't Vector::Clear Remove Elements from a Vector

Why doesn't vector::clear remove elements from a vector?

When you access outside of the bounds of a vector, you get Undefined Behavior. That means anything can happen. Anything.

So you could get the old value, garbage, or a seg-fault. You can't depend on anything.

If you want bounds checking, use the at() member function instead of operator []. It will throw an exception instead of invoking Undefined Behavior.

Erasing() an element in a vector doesn't work

If there are at least 3 items in the vector, to delete the last 3 items is simple -- just call pop_back 3 times:

#include <vector>
#include <iostream>

int main()
{
std::vector<float> v = { 1, 2, 3, 4, 5 };
for (int i = 0; i < 3 && !v.empty(); ++i)
v.pop_back();

for ( const auto &item : v ) std::cout << item << ' ';
std::cout << '\n';
}

Output:

1 2

Calling clear() on a vector does not actually delete data in data()?

The vector's data() method returns a raw pointer to the vector's allocated array in memory. clear() destroys the contents of that array if needed and sets the vector's size() to 0, but does not reallocate the array itself, and thus does not change the vector's capacity(). Calling the vector's shrink_to_fit() method reallocates the array so its capacity() matches its size(), if possible (shrink_to_fit() is advisory only and not guaranteed to actually do anything).

Also, when constructing a std::string from a char* pointer by itself, the char data needs to be null-terminated, but your data is not. You need to push a null terminator into the vector before using data():

void splitString(const string &str, char delimiter, vector<string> * out) {
vector<char> word_buffer;
for (int i = 0; i < str.length(); ++i) {
if (str[i] == delimiter) {
word_buffer.push_back('\0');
out->push_back(word_buffer.data());
word_buffer.clear();
} else {
word_buffer.push_back(str[i]);
}
}
if (!word_buffer.empty()) {
word_buffer.push_back('\0')
out->push_back(word_buffer.data());
}
}

Otherwise, you can simply take the vector's size() into account when constructing the strings, no null terminators needed:

void splitString(const string &str, char delimiter, vector<string> * out) {
vector<char> word_buffer;
for (int i = 0; i < str.length(); ++i) {
if (str[i] == delimiter) {
out->push_back(string(word_buffer.data(), word_buffer.size()));
// alternatively:
// out->emplace_back(word_buffer.data(), word_buffer.size());
word_buffer.clear();
}
else {
word_buffer.push_back(str[i]);
}
}
if (!word_buffer.empty()) {
out->push_back(string(word_buffer.data(), word_buffer.size()));
// alternatively:
// out->emplace_back(word_buffer.data(), word_buffer.size());
}
}

That being said, there are other ways to implement a splitString() function without needing the word_buffer vector at all, eg:

void splitString(const string &str, char delimiter, vector<string> * out) {
string::size_type start = 0, pos = str.find(delimiter);
while (pos != string::npos) {
out->push_back(str.substr(start, pos-start));
start = pos + 1;
pos = str.find(delimiter, start);
}
if (start < str.size()) {
if (start > 0) {
out->push_back(str.substr(start));
} else {
out->push_back(str);
}
}
}

Live Demo

void splitString(const string &str, char delimiter, vector<string> * out) {
istringstream iss(str);
string word;
while (getline(iss, word, delimiter))
out->push_back(std::move(word));
}

Live Demo

But, even if you wanted to buffer the words manually, a std::string would have made more sense than a std::vector<char>, especially since you are outputting std::string values:

void splitString(const string &str, char delimiter, vector<string> * out) {
string word_buffer;
for (string::size_type i = 0; i < str.length(); ++i) {
if (str[i] == delimiter) {
out->push_back(std::move(word_buffer));
word_buffer.clear();
} else {
word_buffer.push_back(str[i]);
}
}
if (!word_buffer.empty()) {
out->push_back(std::move(word_buffer));
}
}

Live Demo

Why does not work vector.clear()

clear removes all elements of the vector as opposed to setting all of them to 0 as you seem to be expecting. After calling clear the size of your vector is 0. Thus when you try to read A[i][j] you are accessing an index out of bounds and anything may happen(your code causes undefined behavior).

Why does vector::erase() not invalidate a reference to the erased element?

I know that references to nothing don't exist

Yes they do: just like a pointer, a reference can be invalidated and left dangling if the object it refers to is destroyed. Using an invalid reference gives undefined behaviour.

why isn't the object destroyed when its owner has dumped it?

It is. But destroying an object doesn't necessarily make the memory it occupied inaccessible. In this case, the memory is still managed by the vector, and accessing it is likely to give you the old value, since nothing has overwritten it. Or it could cause some other form of undefined behaviour.

When will it be destroyed, e.g. when will the memory it occupies be freed?

When the vector is destroyed, or when it reallocates its array, the memory will be freed. It might still be accessible though; small blocks are usually returned to a heap for reuse, and not unmapped from the process's memory space.

My personal guess is as soon as the last reference to the object goes out of scope.

No. As you said, there is no reference counting in C++. Automatic objects are destroyed when they go out of scope, and dynamic objects when they're explicitly destroyed, regardless of any references to them.

Can't delete element from vector, attempting to reference deleted function

If a class has a const member variable, then the copy assignment operator for that class operator= is default-deleted by the compiler.

From https://en.cppreference.com/w/cpp/language/copy_assignment :

A defaulted copy assignment operator for class T is defined as deleted if any of the following is true:

- T has a non-static data member of non-class type (or array thereof) that is const;

This is because the compiler can't guess what it means to copy assign object A into object B, if objects of their class contain a const member variable. Should the compiler just ignore that particular member variable? Should the compiler guess that you mean it to be const, except when copy assigning, then it's okay just this once - I'll look the other way? The compiler has no idea what you would prefer, so it just deletes it.

One option is to explicitly define a copy assignment operator for your class.

However, startingPosition is already declared private, so there's little chance of anything outside of the class inadvertently changing it. I recommend just removing the const specifier.

Why does the copy assignment operator matter? I'm trying to delete things, not copy assign them

When an element is erased from a vector, all elements in the vector "above" the erased element need to be moved down to fill the gap. This happens via the copy assignment operator.

From https://en.cppreference.com/w/cpp/container/vector/erase :

Complexity

Linear: the number of calls to the destructor of T is the same as the number of elements erased, the assignment operator of T is called the number of times equal to the number of elements in the vector after the erased elements

why Vector doesn't provide the remove() member function while list provides?

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.



Related Topics



Leave a reply



Submit