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;
}
Related
I tried to follow the erase example for sample iterator over list but I can't make it work.
Here is the code so far:
for (list<list<string>>::iterator itr = listOfList.begin(), ; itr != listeOfListe.end(); itr++){
if (condition) {
for (list<string>::iterator it6 = itr->begin(); it6 != itr->end(); it6++)
{
itr.erase(*it6);
}
}
}
I get the following error:
class "std::_List_iterator<std::__cxx11::list<std::string, std::allocator<std::string>>>" has no member "erase"
Which indicates that it considers that itr is a list of list of string, why isn't itr simply a list of string since it iterates over listOfList?
You are trying to call erase() on an iterator itself, not on the list that the iterator refers to. That is why you are getting the compiler error. You need to dereference the iterator via operator* or operator-> to access the list to call erase() on.
Also, your inner loop is not accounting for the fact that list::erase() invalidates the specified iterator, so your use of it6++ is undefined behavior after calling erase(it6). You need to use the iterator that erase() returns in order to continue the loop correctly, eg: it6 = erase(it6).
Try something more like this instead:
// pre C++11
for (list<list<string>>::iterator itr = listOfList.begin(); itr != listOfList.end(); ++itr){
if (condition) {
list<string>::iterator it6 = itr->begin();
while (it6 != itr->end()) {
it6 = itr->erase(it6);
}
}
}
// C++11 and later
for (auto &listOfStrings : listOfList){
if (condition) {
auto it6 = listOfStrings.begin();
while (it6 != listOfStrings.end()) {
it6 = listOfStrings.erase(it6);
}
}
}
Which simplifies to this:
// pre C++11
for (list<list<string>>::iterator itr = listOfList.begin(); itr != listOfList.end(); ++itr){
if (condition) {
itr->clear();
}
}
// C++11 and later
for (auto &listOfStrings : listOfList){
if (condition) {
listOfStrings.clear();
}
}
Alternatively:
// pre C++11
if (condition) {
for (list<list<string>>::iterator itr = listOfList.begin(); itr != listOfList.end(); ++itr){
itr->clear();
}
}
// C++11 and later
if (condition) {
for (auto &listOfStrings : listOfList){
listOfStrings.clear();
}
}
I don't think that is what you are looking for, though. Your title says you want to "remove a string from a list", so you are probably looking for something more like this instead:
// pre C++11
for (list<list<string>>::iterator itr = listOfList.begin(); itr != listOfList.end(); ++itr){
if (condition) {
itr->remove(string); // removes all matching strings
}
}
// C++11 and later
for (auto &listOfStrings : listOfList){
if (condition) {
listOfStrings.remove(string); // removes all matching strings
}
}
Alternatively:
// pre C++11
if (condition) {
for (list<list<string>>::iterator itr = listOfList.begin(); itr != listOfList.end(); ++itr){
itr->remove(string); // removes all matching strings
}
}
// C++11 or later
if (condition) {
for (auto &listOfStrings : listOfList){
listOfStrings.remove(string); // removes all matching strings
}
}
Or, maybe you are looking for something more like this instead?
// pre C++11
for (list<list<string>>::iterator itr = listOfList.begin(); itr != listOfList.end(); ++itr){
list<string>::iterator it6 = itr->begin();
while (it6 != itr->end()) {
if (condition(*it6)) {
it6 = itr->erase(it6);
} else {
++it6;
}
}
}
// C++11 and later
for (auto &listOfStrings : listOfList){
auto it6 = listOfStrings.begin();
while (it6 != itr->end()) {
if (condition(*it6)) {
it6 = listOfStrings.erase(it6);
} else {
++it6;
}
}
}
Which, in that latter case, simplifies to this in C++20:
for (auto &listOfStrings : listOfList){
std::erase_if(listOfStrings, [](string &s){ return condition(s); });
}
Please note that behavior of this program is undefined.
for (list<string>::iterator it6 = itr->begin(); it6 != itr->end(); it6++) {
itr->erase(it6); // !!
}
Code marked with // invalidates iterator it6. Incrementing it in the loop is now illegal, since only valid list iterators can be incremented.
To correct the snippet, use following:
for (list<string>::iterator it6 = itr->begin(); it6 != itr->end(); ) {
it6 = itr->erase(it6);
}
In this example, we use the fact that std::list::erase returns the iterator to the next element after the one erased.
However, this is only for illustration, assuming you would want to learn how to work with iterators. Better code would simply be
itr->clear();
Here's my code:
std::list<User>::iterator it;
while (it != allUsers.end())
{
if (it->getId() == userId)
{
allUsers.remove(*it);
return *it;
}
else
{
it++;
}
}
The error I get : list iterators incompatible with erasing
Why?
You have to use erase(), not remove() to remove an element from a list using an iterator:
while (it != allUsers.end()) {
if (it->getId() == userId) {
auto oldvalue = *it;
allUsers.erase(it);
return oldvalue;
}
it++;
}
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;
}
}
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.
I am having an issue and I think it is because of the iterators being invalidated. However I use the iterator from erase() to resume iterating other the structure. When erase() when I try to increment after erase() is called the first time I get the following error
'vector iterator not incrementable '
std::map<uint32_t, std::vector<std::pair<boost::uuids::uuid, tvshared::SecureIPCCallbackHandlePtr>>>::iterator itMap;
std::vector<std::pair<boost::uuids::uuid, tvshared::SecureIPCCallbackHandlePtr>>::iterator itVector;
{
tvstd::lock_guard_mutex l(m_ConnectionsMutex);
itMap = m_Connections.find(static_cast<uint32_t>(pcp->ProcessID()));
if (itMap != m_Connections.end())
{
for (itVector = itMap->second.begin(); itVector != itMap->second.end(); ++itVector)
{
if (commadUUID == itVector->first)
{
itVector->second.reset();
itVector = m_Connections[static_cast<uint32_t>(pcp->ProcessID())].erase(itVector);
}
}
}
}
Can anyone see where I am going wrong?
erase returns an iterator pointing to the new location of the element that followed the last element erased by the function call. This is the container end if the operation erased the last element in the sequence.
so if you erase you do not need to increment your iterator
for (itVector = itMap->second.begin(); itVector != itMap->second.end(); )
{
if (commadUUID == itVector->first)
{
itVector->second.reset();
itVector = m_Connections[static_cast<uint32_t>(pcp->ProcessID())].erase(itVector);
}
else
{
++itVector
}
}
This solved my issue, I just have to call break after i erase but once i erase i do not need to loop to the end of the list. (#Aleexander solution also works)
std::map<uint32_t, std::vector<std::pair<boost::uuids::uuid, tvshared::SecureIPCCallbackHandlePtr>>>::iterator itMap;
std::vector<std::pair<boost::uuids::uuid, tvshared::SecureIPCCallbackHandlePtr>>::iterator itVector;
{
tvstd::lock_guard_mutex l(m_ConnectionsMutex);
itMap = m_Connections.find(static_cast<uint32_t>(pcp->ProcessID()));
if (itMap != m_Connections.end())
{
for (itVector = itMap->second.begin(); itVector != itMap->second.end(); ++itVector)
{
if (commadUUID == itVector->first)
{
itVector->second.reset();
itVector = m_Connections[static_cast<uint32_t>(pcp->ProcessID())].erase(itVector);
break;
}
}
}
}