Differences in erasing an entity from a list in C++ - c++

I am trying to erase an entity from a list in two cases. In the first case the following worked just fine, where I have a list of pairs from which I want to erase a certain one:
bool remove(std::list<std::pair<std::string, size_t>>& myList, std::string const& name) {
for (auto i = myList.begin(); i != myList.end(); i++) {
if(i->first == name) {
myList.erase(i);
return true;
}
}
return false;
}
Here the pair gets removed from the list as it should, but when I have a list of structs it does not work as in the following:
void remove(std::list<myStruct>& myList , const std::string& name) {
for (auto i = myList.begin(); i != myList.end(); i++) {
if(i->name == name) {
myList.erase(i);
}
}
The program crashes in the erase part. Only if I plug in myList.erase(i++) then it works. Why is this??
Have I done something foul in the first case and it just happened to work, but then in the second case it does not? I can not understand the reason.

You're working on an invalidated iterator. That's undefined behavior. That's why erase returns a valid iterator.
If you want to only erase the first matching element, use find_if and then erase if the returned iterator isn't equal to end().
auto it = find_if(myList.begin(), myList.end(), [&name](auto const& p){
return p.first == name;
});
if(it == myList.end()){
return false;
}
myList.erase(it);
return true;
Otherwise, just use erase-remove idiom and be wary for its pitfalls (erase will happily accept 1 argument, but it'll call a different overload):
auto it = remove_if(myList.begin(), myList.end(), [&name](auto const& p){
return p.first == name;
});
myList.erase(it, myList.end());
The above is a generic version (will work if you change myList's type to vector for example), but as per ksfone's reply, std::list<T> implements member function template remove_if:
myList.remove_if([&name](auto const& p){
return p.first == name;
});

Your first loop removes an entry from the list and stops.
for (auto i = myList.begin(); i != myList.end(); i++) {
if(i->first == name) {
myList.erase(i);
return true;
}
}
while your second loop continues looking for matching elements:
for (auto i = myList.begin(); i != myList.end(); i++) {
if(i->name == name) {
myList.erase(i);
}
}
When you erase i from myList, i becomes invalid - we have no idea what it references now that the element it was talking about has gone away and may have been deleted, returned to the os and it's memory used by another thread.
The very next thing you do is i++ which is undefined behavior, since i is an invalid iterator.
The erase operator returns the next valid iterator, so you could write this:
for (auto i = myList.begin(); i != myList.end();) {
if(i->name == name)
i = myList.erase(i);
else
i++;
}
Or you could write:
void remove(std::list<myStruct>& myList , const std::string& name) {
myList.remove_if([&](const myStruct& it) { return it.name == name; });
}
or if your compiler supports C++14
void remove(std::list<myStruct>& myList , const std::string& name) {
myList.remove_if([&](auto& it) { return it.name == name; });
}
As in
#include <iostream>
#include <list>
#include <string>
struct A {
std::string name_;
A(const char* name) : name_(name) {}
};
void dump(const std::list<A>& list) {
for (auto&& a : list) {
std::cout << a.name_ << ' ';
}
std::cout << '\n';
}
int main() {
std::list<A> myList { { "hello", "pizza", "world", "pizza" } };
dump(myList);
const std::string name = "pizza";
myList.remove_if([&](const A& it){ return it.name_ == name; });
dump(myList);
return 0;
}
Live demo: http://ideone.com/SaWejv

erase() invalidates the erased iterator. The for loop then attempts to increment the invalidated iterator, resulting in undefined behavior, and a crash.
The correct way to do this, actually, would be:
i=erase(i);
rather than using post-increment.

I'll avoid repeating what others have stated and instead suggest a more elegant solution via the erase-remove idiom:
myList.erase(std::remove_if(myList.begin(), myList.end(), [&name](auto& el) {
return el.first == name;
}), myList.end());

The reason is that the iterator breaks when you remove an item, but in the first case you are returning back so the error never occurs in the next iteration. In any way it's not a matter of your struct.

Related

Is it possible to remove elements from a vector of shared_ptr?

Say I have
vector<shared_ptr<string>> enemy;
how do I remove elements from the enemy vector?
Thanks for your help in advance
**Edit (code in context)
void RemoveEnemy( vector<shared_ptr<Enemy>> & chart, string id )
{
int i = 0;
bool found = FALSE;
for(auto it = chart.begin(); it != chart.end(); i++)
{
if(id == chart[i]->GetEnemyID() )
{
found = TRUE;
chart.erase(it);
}
}
the code above segfaults me
You remove elements the same way you remove any elements from any std::vector - via the std::vector::erase() method, for instance. All you need for that is an iterator to the desired element to remove.
In your case, since you are storing std::shared_ptr<std::string> objects rather than storing actual std::string objects, you may need to use something like std::find_if() to find the vector element containing the desired string value, eg:
void removeEnemy(string name)
{
auto iter = std::find_if(enemy.begin(), enemy.end(),
[&](auto &s){ return (*s == name); }
);
if (iter != enemy.end())
enemy.erase(iter);
}
UPDATE: in the new code you have added, you are incorrectly mixing indexes and iterators together. You are creating an infinite loop if the vector is not empty, as you never increment the it iterator that controls your loop, you are incrementing your index i variable instead (see what happens when you don't give your variables unique and meaningful names?). So you end up going out of bounds of the vector into surrounding memory. That is why you get the segfault error.
Even though you are (trying to) use an iterator to loop through the vector, you are using indexes to access the elements, instead of dereferencing the iterator to access the elements. You don't need to use indexes at all in this situation, the iterator alone will suffice.
Try this instead:
void RemoveEnemy( vector<shared_ptr<Enemy>> & chart, string id )
{
for(auto it = chart.begin(); it != chart.end(); ++it)
{
if (id == it->GetEnemyID() )
{
chart.erase(it);
return;
}
}
Or, using the kind of code I suggested earlier:
void RemoveEnemy( vector<shared_ptr<Enemy>> & chart, string id )
{
auto iter = std::find_if(chart.begin(), chart.end(),
[&](auto &enemy){ return (enemy->GetEnemyID() == id); }
);
if (iter != chart.end())
chart.erase(iter);
}
The problem with your code is that erase() invalidates the iterator. You must use it = chart.erase(it).
I like mine which will remove aliens at high speed and without any care for the ordering of the other items. Removal with prejudice!
Note: remove_if is most often used with erase and it will preserve the order of the remaining elements. However, partition does not care about the ordering of elements and is much faster.
partition-test.cpp:
make partition-test && echo 1 alien 9 alien 2 8 alien 4 7 alien 5 3 | ./partition-test
#include <algorithm>
#include <iostream>
#include <iterator>
#include <memory>
#include <string>
#include <vector>
using namespace std;
template <typename T>
ostream &operator<<(ostream &os, const vector<T> &container) {
bool comma = false;
for (const auto &x : container) {
if (comma)
os << ", ";
os << *x;
comma = true;
}
return os;
}
int main() {
vector<shared_ptr<string>> iv;
auto x = make_shared<string>();
while (cin >> *x) {
iv.push_back(x);
x = make_shared<string>();
}
cout << iv << '\n';
iv.erase(partition(begin(iv), end(iv),
[](const auto &x) { return *x != "alien"s; }),
end(iv));
cout << iv << '\n';
return 0;
}

Range-based for loop with special case for the first item

I find myself often with code that looks like this:
bool isFirst = true;
for(const auto &item: items)
{
if(!isFirst)
{
// Do something
}
// Normal processing
isFirst = false;
}
It seems that there ought to be a better way to express this, since it's a common pattern in functions that act like a "join".
Maybe a for_first_then_each is what you're looking for? It takes your range in terms of iterators and applies the first function to the first element and the second function to the rest.
#include <iostream>
#include <vector>
template<typename BeginIt, typename EndIt, typename FirstFun, typename OthersFun>
void for_first_then_each(BeginIt begin, EndIt end, FirstFun firstFun, OthersFun othersFun) {
if(begin == end) return;
firstFun(*begin);
for(auto it = std::next(begin); it != end; ++it) {
othersFun(*it);
};
}
int main() {
std::vector<int> v = {0, 1, 2, 3};
for_first_then_each(v.begin(), v.end(),
[](auto first) { std::cout << first + 42 << '\n'; },
[](auto other) { std::cout << other - 42 << '\n'; }
);
// Outputs 42, -41, -40, -39
return 0;
}
You can't know which element you are visiting in a range based for loop unless you are looping over a container like an array or vector where you can take the address of the object and compare it to the address of the first item to figure out where in the container you are. You can also do this if the container provides lookup by value, you can see if the iterator returned from the find operation is the same as the begin iterator.
If you need special handling for the first element then you can fall back to a traditional for loop like
for (auto it = std::begin(items), first = it, end = std::end(items); it != end; ++it)
{
if (it == first)
{
// do something
}
// Normal processing
}
If what you need to do can be factored out of the loop then you could use a range based for loop and just put the processing before the loop like
// do something
for(const auto &item: items)
{
// Normal processing
}
With Ranges coming in C++20, you can split this in two loops:
for (auto const& item : items | view::take(1)) {
// first element only (or never executed if items is empty)
}
for (auto const& item : items | view::drop(1)) {
// all after the first (or never executed if items has 1 item or fewer)
}
If you don't want to wait for C++20, check out range-v3 which supports both of these operations.
This won't work like this with an Input range (like if items is really a range that reads from cin) but will work just fine with any range that is Forward or better (I'm guessing items is a container here, so that should be fine).
A more straightforward version is actually to use enumerate (which only exists in range-v3, not in C++20):
for (auto const& [idx, item] : view::enumerate(items)) {
if (idx == 0) {
// first element only
}
// all elements
}
A fun alternative solution, that I would not use in production without great care, would be to use custom iterator.
int main() {
std::vector<int> v{1,2,3,4};
for (const auto & [is_first,b] : wrap(v)) {
if (is_first) {
std::cout << "First: ";
}
std::cout << b << std::endl;
}
}
A toy implementation could look like this:
template<typename T>
struct collection_wrap {
collection_wrap(T &c): c_(c) {}
struct magic_iterator {
bool is_first = false;
typename T::iterator itr;
auto operator*() {
return std::make_tuple(is_first, *itr);
}
magic_iterator operator++() {
magic_iterator self = *this;
itr++;
//only works for forward
is_first = false;
return self;
}
bool operator!=(const magic_iterator &o) {
return itr != o.itr;
}
};
magic_iterator begin() {
magic_iterator itr;
itr.is_first = true;
itr.itr = c_.begin();
return itr;
}
magic_iterator end() {
magic_iterator itr;
itr.is_first = false;
itr.itr = c_.end();
return itr;
}
T &c_;
};
template<typename Collection>
collection_wrap<Collection>
wrap(Collection &vec) {
return collection_wrap(vec);
}
Check the object address to see if it's the first item:
for(const auto &item: items)
{
if (&item != &(*items.begin())
{
// do something for all but the first
}
// Normal processing
}
An approach still valid in C++ is to use a macro:
#include <iostream>
#include <vector>
#define FOR(index, element, collection, body) { \
auto &&col = collection; \
typeof(col.size()) index = 0; \
for(auto it=col.begin(); it!=col.end(); index++, it++) { \
const auto &element = *it; \
body; \
} \
}
using namespace std;
int main() {
vector<int> a{0, 1, 2, 3};
FOR(i, e, a, {
if(i) cout << ", ";
cout << e;
})
cout << endl;
FOR(i, e, vector<int>({0, 1, 2, 3}), {
if(i) cout << ", ";
cout << e;
})
cout << endl;
return 0;
}
Prints:
0, 1, 2, 3
0, 1, 2, 3
This solution is succinct compared to alternative options. On the downside, index is being tested and incremented on each iteration of the loop - this can be avoided by increasing the complexity of the macro and by using bool first instead of index, but using index in the macro covers more use cases than bool first.
Since C++20, you can slightly improve your range-based for loop by using an init-statement. The init-statement allows you to move your isFirst flag into the scope of the loop so that this flag is no longer visible outside the loop:
std::vector<int> items { 1, 2, 3 };
for(bool isFirst(true); const auto &item: items) {
if(!isFirst) {
std::cout << "Do something with: " << item << std::endl;
}
std::cout << "Normal processing: " << item << std::endl;
isFirst = false;
}
Output:
Normal processing: 1
Do something with: 2
Normal processing: 2
Do something with: 3
Normal processing: 3
Code on Wandbox
I assume you want to know how to retrieve the first element, you could do this with an array and a vector.
I'm going to show the array here.
First include this in your code:
#include <array>
Then convert your array accordingly:
std::array<std::string, 4> items={"test1", "test2", "test3", "test4"};
for(const auto &item: items)
{
if(item == items.front()){
// do something
printf("\nFirst: %s\n", item.c_str()); //or simply printf("\nFirst:"); since you gonna output a double element
}
// Normal processing
printf("Item: %s\n", item.c_str());
}
return 0;
}

How to remove key from poco json while iterating it?

How do I remove key from a Poco json while iterating it? Like:
Poco::JSON::Object::Ptr poco_json;
for (auto& objs : *poco_json)
{
// do something
if (objs.first == "specific key")
poco_json->remove(key);
}
or
Poco::JSON::Object::Ptr poco_json;
for(auto it = poco_json->begin();it != poco_json->end();)
{
// do something
if (it->first == "specific key")
it = poco_json->remove(it->first);//error : poco didn't have something like this
else
++it;
}
the problem is after remove a key from the json, it will invalidate the iterators. I know that in std::map, erase return the valid iterator for next iteration, but I cant find something similar for Poco json.
std::map::erase returns iterator to next item since C++11, before c++11 you erase items in this way:
for (auto it = m.begin(); it != m.end(); ) {
if (it->first == someKey)
m.erase(it++); // use post-increment,pass copy of iterator, advance it
else
++it;
}
and you can do it in similar way while erasing key from Poco::JSON::Object. Where did you read that remove invalidates iterators?
Some snippet code from source:
class JSON_API Object {
typedef std::map<std::string, Dynamic::Var> ValueMap; // <--- map
//...
Iterator begin();
/// Returns begin iterator for values.
Iterator end();
/// Returns end iterator for values.
void remove(const std::string& key);
/// Removes the property with the given key.
ValueMap _values; // <---
};
inline Object::Iterator Object::begin()
{
return _values.begin();
}
inline Object::Iterator Object::end()
{
return _values.end();
}
inline void Object::remove(const std::string& key)
{
_values.erase(key); // <--- erase is called on map, so iteratos are not invalidated
if (_preserveInsOrder)
{
KeyList::iterator it = _keys.begin();
KeyList::iterator end = _keys.end();
for (; it != end; ++it)
{
if (key == (*it)->first)
{
_keys.erase(it);
break;
}
}
}
_modified = true;
}
You could rewrite your loop into:
for(auto it = poco_json->begin();it != poco_json->end();)
{
// do something
if (it->first == "specific key")
{
auto copyIt = it++;
poco_json->remove(copyIt->first);
}
else
++it;
}
EDIT
Why your code doesn't work on range-for loop:
for (auto& objs : *poco_json)
{
// do something
if (objs.first == "specific key")
poco_json->remove(key);
}
it is translated into
for (auto it = poco_json->begin(); it != poco_json->end(); ++it)
{
// do something
if (it->first == "specific key")
poco_json->remove(it->first);
// remove is called, it is erased from inner map
// ++it is called on iterator which was invalidated,
// code crashes
}
You can modify this code in Poco:
inline Iterator Object::remove(const std::string& key)
{
auto ret_it = _values.erase(key);
if (_preserveInsOrder)
{
KeyList::iterator it = _keys.begin();
KeyList::iterator end = _keys.end();
for (; it != end; ++it)
{
if (key == (*it)->first)
{
_keys.erase(it);
break;
}
}
}
_modified = true;
return ret_it;
}

How to delete an object from a map which contains a vector as value in C++

I have a map which contains a of vector of type Messages.
std::map<std::string, std::vector<Message>> storage;
class Message has 3 member variables.
class Message
{
private:
std::string msg;
std::string msg_type;
int priority;
}
Now i am trying to delete an object which has priority(say 3) from the map. i am using the following function for it. But it doesn't work.
void deleteByMessagePriority(int priority)
{
if (checkPriorityOfMessage(priority))
{
for (std::map<std::string, std::vector<Message>>::iterator it = storage.begin(); it != storage.end(); it++)
{
std::vector<Message> listOfMsgs = it->second;
for (std::vector<Message>::iterator vec_it = listOfMsgs.begin(); vec_it != listOfMsgs.end(); vec_it++)
//for(int index = 0;index < listOfMsgs.size();index++)
{
if (vec_it->getPriority() == priority)
{
listOfMsgs.pop_back();
}
}
}
}
}
Look carefully at this:
if (vec_it->getPriority() == priority)
{
listOfMsgs.pop_back();
}
You're looking at the priority of one message (the one referred to by vec_it), but then what are you deleting if it matches?
Instead of writing your own loop, I'd use erase and std::remove_if to remove all the items you care about in that vector at once.
for (auto & item : storage) {
auto &vec = item.second;
auto start_junk = std::remove_if(
vec.begin(), vec.end(),
[=](Message const &m) { return m.priority == priority; });
vec.erase(start_junk, vec.end());
}
if (vec_it->getPriority() == priority)
{
listOfMsgs.pop_back();
pop_back() removes the last element of the vector which you don't want.You want to check erase
Also remember erase() invalidates the iterators so you need iterator to the next element after a deleted element for which we can fortunately use return value of erase
if (vec_it->getPriority() == priority)
{
vec_it = listOfMsgs.erase(vec_it); //returns iterator to the element after vec_it which can also be `listOfMsgs.end()`
std::vector<Message> listOfMsgs = it->second;
.
.
.
listOfMsgs.pop_back();
You're copying the list, only to modify the copy. What you meant is:
std::vector<Message>& listOfMsgs = it->second;
Then you can proceed erasing elements. As Gaurav Sehgal says, use the return value of erase:
std::vector<Message>::iterator vec_it = listOfMsgs.begin();
while (vec_it != listOfMsgs.end())
{
if (vec_it->getPriority() == priority)
{
vec_it = listOfMsgs.erase(vec_it);
}
else
{
++vec_it;
}
}

Converting const auto & to iterator

A number of posts I've read lately claim for(const auto &it : vec) is the same as using the longer iterator syntax for(std::vector<Type*>::const_iterator it = vec.begin(); it != vec.end(); it++). But, I came upon this post that says they're not the same.
Currently, I'm trying to erase an element in a for loop, after it is used, and wondering if there is any way to convert const auto &it : nodes to std::vector<txml::XMLElement*>::iterator?
Code in question:
std::vector<txml2::XMLElement *> nodes;
//...
for (const auto &it : nodes)
{
//...
nodes.erase(it);
}
I pretty sure I could just rewrite std::vector<txml2::XMLElement*> as a const pointer, but would prefer not to since this code is just for debugging in the moment.
You should not be attempting to convert the range declaration in your range based for loop to an iterator and then deleting it whilst iterating. Even adjusting iterators while iterating is dangerous, and you should instead rely on algorithms.
You should use the Erase-remove idom.
You can use it with remove_if.
It would look something like:
nodes.erase( std::remove_if(nodes.begin(), nodes.end(), [](auto it){
//decide if the element should be deleted
return true || false;
}), nodes.end() );
Currently in the technical specifications, is erase_if.
This is a cleaner version of the same behaviour shown above:
std::erase_if(nodes,[](auto it){
//decide if the element should be deleted
return true || false;
});
You don't get an iterator but a reference to the element. Unless you want to do a std::find with it, it's pretty hard to get an iterator out of it.
Vectors are nice, so you could increase a counter per element and do nodes.begin() + counter to get the iterator, but it'd sort of defeat the point.
Also erasing the iterator in the for loop will result in you iterating after the end of the vector, you can test this code:
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v = {0,1,2,3,4,5,6};
for (int x : v) {
cout << x << endl;
if (x == 2) {
v.erase(v.begin() + 2);
}
}
return 0;
}
If you want to use iterators, just do a loop with them, if in addition you want to erase one mid-loop you have to follow this answer:
for (auto it = res.begin() ; it != res.end(); ) {
const auto &value = *it;
if (condition) {
it = res.erase(it);
} else {
++it;
}
}
Note that you don't need to specify the whole type of the iterator, auto works just as well.