How to erase an element from a vector<shared_ptr> - c++

I´m buiding a sort of a registry class using the following code:
class MyClass
{
public:
int a;
std::string b;
};
class Register
{
public:
std::vector<std::shared_ptr<MyClass>> items;
bool registerItem(std::shared_ptr<MyClass> item)
{
/*
* Check if item exists
*/
auto position = std::find(items.begin(), items.end(), item);
if (position != items.end())
return false;
items.push_back(item);
return true;
}
bool unregisterItem(std::shared_ptr<MyClass> item)
{
auto position = std::find(items.begin(), items.end(), item);
if (position == items.end())
return false;
items.erase(item);
return true;
}
};
int main()
{
std::shared_ptr<MyClass> item1 = new MyClass;
Register registry;
if (!registry.registerItem(item1))
std::cout << "Error registering item1" << std::endl;
else
std::cout << "Success registering item1" << std::endl;
if (!registry.registerItem(item1))
std::cout << "Error unregistering item1" << std::endl;
else
std::cout << "Success unregistering item1" << std::endl;
}
I can´t compile this code, as items.erase(item) complains about error: no matching member function for call to 'erase'.
Why I can´t erase the object I´ve added. What is the correct way to remove a std::shared_ptr from a std::vector ?

Because you want to use an iterator into items (i.e. position), so:
items.erase(position);

There are two declarations available. From cppreference:
iterator erase (const_iterator position);
iterator erase (const_iterator first, const_iterator last);
You try to std::vector::erase the item itsself instead of the iterator. Use
items.erase(position);
instead.

You have to call erase on position, not item
since erase is used on iterators

You should pass to erase iterator not a value, there is nothing special with deleting std::shared_ptr. Also you have mistype in code: you register item twice instead of unregister it in the second call.

Related

erase map element using an iterator inside a function

I need to erase map elements using an iterator inside a function which takes map iterator as argument, but getting runtime error(free(): double free detected). This is my code:
#include <iostream>
#include <map>
std::map<int32_t, int32_t> mapp;
void erasee(std::map<int32_t, int32_t>::iterator itr) {
itr = mapp.erase(itr);
}
int main()
{
mapp.emplace(1, 1000);
std::map<int32_t, int32_t>::iterator itr = mapp.begin();
while(itr != mapp.end()) {
std::cout << "before" << std::endl;
erasee(itr);
std::cout << "after" << std::endl;
}
return 0;
}
itr is passed to erasee by value. Consequently reassigning it has no effect after erasee returns.
As a quick fix you can pass it by reference:
void erasee(std::map<int32_t, int32_t>::iterator & itr) {
Though returning a new iterator might be a more readable solution.

Delete element from a iterating list from an event

Wanted to know what is the best way to delete an element from a list which is being iterating continuously in an event loop.
I have an event loop where my list is getting updated
for (auto pIt = this->particles.begin(); pIt != this->particles.end(); pIt++) {
(*pIt)->Update(system, gameTime);
}
At a certain event based time I have to remove a random element from the list.
It is handled in a separate function using:
this->particles.remove_if([particle](Particle *ptc)->bool {
return ptc == particle;
});
This gives a list iteration runtime error because the iterators in the loop become invalid. What is the optimal way to solve such kind of situation?
I am assuming the deleting of the element happens inside the Update() function and the "list" is actually std::list and not a std::vector.
Do not perform pIt++ in the loop header. Instead, do pIt++ BEFORE calling Update(). Call Update() on the original object that is returned:
for (auto pIt = this->particles.begin(); pIt != this->particles.end(); ) {
auto pForCall = *(pIt++);
pForCall->Update(system, gameTime);
}
Another case where this will not work is when the element being delete is not the element we're calling Update on.
The erasing function must have access to the iterator used in the loop (make it a member variable) and if that iterator points to the element being deleted increment the iterator. This way you keep the loop iterator from being invalidated by erasing.
Example:
#include <iostream>
#include <list>
struct Explosion;
struct Particle {
void update(Explosion&);
};
struct Explosion {
std::list<Particle> particles;
std::list<Particle>::iterator particle_iter;
void update() {
for(particle_iter = particles.begin(); particle_iter != particles.end(); ) {
auto to_call = particle_iter++;
to_call->update(*this);
}
}
template<class Condition>
void filter(Condition condition) {
for(auto i = particles.begin(); i != particles.end();) {
auto to_remove = i++;
if(condition(*to_remove)) {
// Remove the element.
if(particle_iter == to_remove)
particle_iter = i;
particles.erase(to_remove);
}
}
}
};
void Particle::update(Explosion& explosion) {
explosion.filter([this](Particle& other) { return this == &other; });
// The above ends up destroying this object.
// Cannot no longer access this and should only return.
}
int main() {
Explosion explosion{{Particle{}, Particle{}}, {}};
std::cout << explosion.particles.size() << '\n';
explosion.update();
std::cout << explosion.particles.size() << '\n';
}

debug assert vector iterator not dereferncable with find_if

I want to convert my for loop into a find_if lambda but I Always get the same result vector iterator not dereferncable.
void SearchObjectDescription(std::vector<std::string>& input, Player & player)
{
//--local variable
bool found = false;
//std::find_if
std::vector<std::string>::iterator i = std::find_if(input.begin(), input.end(),[&](Player player)
{
if ((player.InInventory((*i)) ) == true)
{
std::cout << (player.GetInventory().ItemByName((*i))).ExamineObject() << std::endl;
return true;
}
else
{
std::cout << "Object not in invetory!" << std::endl;
return false;
}
});
//original for loop
//for (std::vector<std::string>::iterator i = input.begin()+1; i != input.end(); i++)
//{
// if (player.InInventory((*i))== true)
// {
// std::cout << (player.GetInventory().ItemByName((*i))).ExamineObject() << std::endl;
// found = true;
// break;
// }
//}
//if (found ==false)
//{
// std::cout << "Object not in invetory!" << std::endl;
//}
}
Can some one help me please?
You are thinking about the lambda wrongly. The std::find_if function takes, as it's third argument, a lambda that takes an element of the vector, and return if it's the element you searched for.
The purpose of the lambda is to take an element and tell if it's the right one.
Yet, your lambda don't receive an element of your vector of string, but takes a player as parameter. But you obviously don't have a list of player, you have a list of string. Why should the element to check should be a Player?
Instead, capture your player variable and receive the element to check. It would look like this:
void SearchObjectDescription(std::vector<std::string>& input, Player & player)
{
auto i = std::find_if(input.begin(), input.end(),[&](const std::string& item)
{
// item is equal to the current element being checked
// player is captured by reference because of [&]
// if the item `item` is in inventory, return true.
return player.InInventory(item);
});
if (i != input.end()) {
// if it's not equal to the end, *i is the found item.
}
}
Note that in C++14, you can receive auto&& in your lambda, and it will be deduced to string&:
std::find_if(input.begin(), input.end(), [&](auto&& item)
{
// type of item is std::string&
// ...
});
You cannot use the i iterator inside of your lambda, because it is not initialized until after std::find_if() exits. You need to use the input parameter of the lambda instead, which will be a std::string from the vector, not a Player object.
Also, you are not checking the return value of std::find_if() to make sure you have a valid iterator before dereferencing it.
You did not translate your for loop to a lambda-based std::find_if() correctly. Try this instead:
void SearchObjectDescription(std::vector<std::string>& input, Player & player)
{
auto i = std::find_if(input.begin()+1, input.end(),
[&](const std::string &s) { return player.InInventory(s); });
if (i != input.end())
{
std::cout << (player.GetInventory().ItemByName(*i)).ExamineObject() << std::endl;
}
else
{
std::cout << "Object not in inventory!" << std::endl;
}
}

How to return null pointer for list member c++?

I need to return pointer for a member in a list, and if the required member is not there I get a null pointer (or any other indication)
list<LINKS>::iterator find_link(list<LINKS> &link, char* ID)
{
list<LINKS>::iterator link_index;
LINKS link_i;
link_index = link.begin();
for (int i = 0; i < link.size(), i++)
{
link_i = *link_index;
if (!strcmp(link_i.ID, ID)) { return link_index; };
link_index++;
};
cout << "Node outsession does not exist " << ID;
return nullptr; // ERROR (nullptr is not the same type as list<LINKS>::iterator)
};
I added the error message (cout << "Node outsession does not exist " << ID;) to indicate if the link is not found, but I need to indicate that for the caller function.
How can I do that ?
Thanks
nullptr is not an iterator, so you can't return it in a function that returns an iterator value
std::find(begin(), end(), value) returns an iterator and returns end() if the value is not found. To check it you say:
std::list<int> l;
...
auto found = std::find(l.begin(), l.end(), 6); // returns an std::list::iterator
if (found == l.end())
{
std::cout << "Bogus!" << std::endl;
}
...
There are other examples in which the container find returns a value, and uses a special value for "not found"
std::string s = "go to the store";
auto found = s.find('x'); // returns a size_t index into the string
if (found == std::string::npos)
{
std::cout << "Bogus!" << std::endl;
}
Returning nullptr would make sense if your function returned a pointer. In that case, nullptr would be a reasonable sentinel value for not found.
The normal accepted way to return 'value not found' when searching a list is to return an iterator pointing to the end of the list. In this case link.end()
This is exactly how the std::find algorithm from <algorithm> behaves.
In your calling function test the returned iterator as follows:
list<LINKS>::iterator itr = find_link(list, "Funyuns");
if( itr == list.end() ) {
std::cout << "You are all out of Funyons :-(" << std::endl;
}

How to find if a given key exists in a C++ std::map

I'm trying to check if a given key is in a map and somewhat can't do it:
typedef map<string,string>::iterator mi;
map<string, string> m;
m.insert(make_pair("f","++--"));
pair<mi,mi> p = m.equal_range("f");//I'm not sure if equal_range does what I want
cout << p.first;//I'm getting error here
so how can I print what is in p?
Use map::find and map::end:
if (m.find("f") == m.end()) {
// not found
} else {
// found
}
To check if a particular key in the map exists, use the count member function in one of the following ways:
m.count(key) > 0
m.count(key) == 1
m.count(key) != 0
The documentation for map::find says: "Another member function, map::count, can be used to just check whether a particular key exists."
The documentation for map::count says: "Because all elements in a map container are unique, the function can only return 1 (if the element is found) or zero (otherwise)."
To retrieve a value from the map via a key that you know to exist, use map::at:
value = m.at(key)
Unlike map::operator[], map::at will not create a new key in the map if the specified key does not exist.
C++20 gives us std::map::contains to do that.
#include <iostream>
#include <string>
#include <map>
int main()
{
std::map<int, std::string> example = {{1, "One"}, {2, "Two"},
{3, "Three"}, {42, "Don\'t Panic!!!"}};
if(example.contains(42)) {
std::cout << "Found\n";
} else {
std::cout << "Not found\n";
}
}
You can use .find():
map<string,string>::iterator i = m.find("f");
if (i == m.end()) { /* Not found */ }
else { /* Found, i->first is f, i->second is ++-- */ }
C++17 simplified this a bit more with an If statement with initializer.
This way you can have your cake and eat it too.
if ( auto it{ m.find( "key" ) }; it != std::end( m ) )
{
// Use `structured binding` to get the key
// and value.
const auto&[ key, value ] { *it };
// Grab either the key or value stored in the pair.
// The key is stored in the 'first' variable and
// the 'value' is stored in the second.
const auto& mkey{ it->first };
const auto& mvalue{ it->second };
// That or just grab the entire pair pointed
// to by the iterator.
const auto& pair{ *it };
}
else
{
// Key was not found..
}
m.find == m.end() // not found
If you want to use other API, then find go for m.count(c)>0
if (m.count("f")>0)
cout << " is an element of m.\n";
else
cout << " is not an element of m.\n";
I think you want map::find. If m.find("f") is equal to m.end(), then the key was not found. Otherwise, find returns an iterator pointing at the element found.
The error is because p.first is an iterator, which doesn't work for stream insertion. Change your last line to cout << (p.first)->first;. p is a pair of iterators, p.first is an iterator, p.first->first is the key string.
A map can only ever have one element for a given key, so equal_range isn't very useful. It's defined for map, because it's defined for all associative containers, but it's a lot more interesting for multimap.
template <typename T, typename Key>
bool key_exists(const T& container, const Key& key)
{
return (container.find(key) != std::end(container));
}
Of course if you wanted to get fancier you could always template out a function that also took a found function and a not found function, something like this:
template <typename T, typename Key, typename FoundFunction, typename NotFoundFunction>
void find_and_execute(const T& container, const Key& key, FoundFunction found_function, NotFoundFunction not_found_function)
{
auto& it = container.find(key);
if (it != std::end(container))
{
found_function(key, it->second);
}
else
{
not_found_function(key);
}
}
And use it like this:
std::map<int, int> some_map;
find_and_execute(some_map, 1,
[](int key, int value){ std::cout << "key " << key << " found, value: " << value << std::endl; },
[](int key){ std::cout << "key " << key << " not found" << std::endl; });
The downside to this is coming up with a good name, "find_and_execute" is awkward and I can't come up with anything better off the top of my head...
map<string, string> m;
check key exist or not, and return number of occurs(0/1 in map):
int num = m.count("f");
if (num>0) {
//found
} else {
// not found
}
check key exist or not, and return iterator:
map<string,string>::iterator mi = m.find("f");
if(mi != m.end()) {
//found
//do something to mi.
} else {
// not found
}
in your question, the error caused by bad operator<< overload, because p.first is map<string, string>, you can not print it out. try this:
if(p.first != p.second) {
cout << p.first->first << " " << p.first->second << endl;
}
Be careful in comparing the find result with the the end like for map 'm' as all answer have
done above
map::iterator i = m.find("f");
if (i == m.end())
{
}
else
{
}
you should not try and perform any operation such as printing the key or value with iterator i if its equal to m.end() else it will lead to segmentation fault.
Comparing the code of std::map::find and std::map::count, I'd say the first may yield some performance advantage:
const_iterator find(const key_type& _Keyval) const
{ // find an element in nonmutable sequence that matches _Keyval
const_iterator _Where = lower_bound(_Keyval); // Here one looks only for lower bound
return (_Where == end()
|| _DEBUG_LT_PRED(this->_Getcomp(),
_Keyval, this->_Key(_Where._Mynode()))
? end() : _Where);
}
size_type count(const key_type& _Keyval) const
{ // count all elements that match _Keyval
_Paircc _Ans = equal_range(_Keyval); // Here both lower and upper bounds are to be found, which is presumably slower.
size_type _Num = 0;
_Distance(_Ans.first, _Ans.second, _Num);
return (_Num);
}
I know this question already has some good answers but I think my solution is worth of sharing.
It works for both std::map and std::vector<std::pair<T, U>> and is available from C++11.
template <typename ForwardIterator, typename Key>
bool contains_key(ForwardIterator first, ForwardIterator last, Key const key) {
using ValueType = typename std::iterator_traits<ForwardIterator>::value_type;
auto search_result = std::find_if(
first, last,
[&key](ValueType const& item) {
return item.first == key;
}
);
if (search_result == last) {
return false;
} else {
return true;
}
}
map <int , char>::iterator itr;
for(itr = MyMap.begin() ; itr!= MyMap.end() ; itr++)
{
if (itr->second == 'c')
{
cout<<itr->first<<endl;
}
}
If you want to compare pair of map you can use this method:
typedef map<double, double> TestMap;
TestMap testMap;
pair<map<double,double>::iterator,bool> controlMapValues;
controlMapValues= testMap.insert(std::pair<double,double>(x,y));
if (controlMapValues.second == false )
{
TestMap::iterator it;
it = testMap.find(x);
if (it->second == y)
{
cout<<"Given value is already exist in Map"<<endl;
}
}
This is a useful technique.