Getting index of a std::vector<std::string>::iterator - c++

So that's what I have tried so far:
class menu_item
{
private:
// ....
std::vector<std::string> options_;
std::vector<std::string>::iterator current_;
public:
menu_item(std::string name, std::vector<std::string> options)
: name_(name), options_(options)
{
current_ = begin(options_);
}
// ....
const int curr_opt_id()
{
return current_ - begin(options_);
}
};
But curr_opt_id() returns -24. Does anybody know what I am doing wrong here?

When you add to a vector, there's a chance that the internal storage will be reallocated which will invalidate all existing iterators. Doing arithmetic on an invalid iterator isn't going to end well.
See Iterator invalidation rules

Iterators of a vector get invalidated upon reallocation, which happens when the current capacity is not sufficient to hold the actual content plus a newly added element.
What is most likely happening here is that the current_ iterator, which is initialized at construction time, gets invalidated by subsequent insertions into options_, which gives you undefined behavior when evaluating the expression:
current_ - begin(options_)

Related

std::map::try_emplace return value results in a dangling pointer when retrieving a unique_ptr

I have a factory function that returns a non-owning pointer to the created object after it is inserted into a resource-owning map to be later stored as a non-owning pointer elsewhere; however, by using the return value of std::map::try_emplace which should be an iterator to what was/is inserted, this causes the internal contents of the std::unique_ptr to be invalid when accessed through the referring non-owning pointer.
Instead, I have to call std::map::find and basically search for a value twice.
I don't quite understand what's wrong with the non-working version of the code below.
Shouldn't the return value of std::map::try_emplace be directly usable/copyable?
What I wanted to do, but doesn't work:
std::map<std::string, std::unique_ptr<Feature>> Feature::s_registry{};
Feature* Feature::CreateFeature(Map* map, const XMLElement& elem) {
auto new_feature = std::make_unique<Feature>(map, elem);
//Copy name out so it isn't erased via the move operation.
std::string new_feature_name = new_feature->name;
if(auto&& [where, inserted] = s_registry.try_emplace(new_feature_name, std::move(new_feature)); inserted) {
return where->second.get();
} else {
if(where != std::end(s_registry)) {
return where->second.get();
}
}
return nullptr;
}
Instead I have to call find to get a valid iterator:
std::map<std::string, std::unique_ptr<Feature>> Feature::s_registry{};
Feature* Feature::CreateFeature(Map* map, const XMLElement& elem) {
auto new_feature = std::make_unique<Feature>(map, elem);
//Copy name out so it isn't erased via the move operation.
std::string new_feature_name = new_feature->name;
if(const auto where_inserted = s_registry.try_emplace(new_feature_name, std::move(new_feature)); where_inserted.second) {
if(const auto found = s_registry.find(new_feature_name); found != std::end(s_registry)) {
return found->second.get();
}
} else {
if(const auto found = s_registry.find(new_feature_name); found != std::end(s_registry)) {
return found->second.get();
}
}
return nullptr;
}
The code can be as simple as
Feature* Feature::CreateFeature(Map* map, const XMLElement& elem)
{
auto new_feature = std::make_unique<Feature>(map, elem);
return s_registry.emplace(new_feature->name, std::move(new_feature)).first->second.get();
}
If the new_feature was not inserted because the slot is already occupied, .first points to the already existing value. Otherwise, it points to the newly inserted object. In both cases, that object's ->second should be valid.
Furthermore, std::move does not move anything. The members that new_feature points to can be used until the final destination is initialized, at which point the std::map will already know where to insert the value. Therefore, it is not necessary to keep new_feature->name in a separate value. There is some discussion of this behavior in this post.

Why the exception is thrown on std::deque.erase()?

This throws when trying to remove element from deque via iterator. The error is "can not seek value-initialized iterator" using VS2017. I wonder why this is happening, isn't std::deque a doubly linked list that does not invalidate iterators on push_front() / push_back()?
class deque2 {
public:
bool enqueue(int val) {
if (mp.find(val) != mp.end()) {
return false;
}
dq.push_front(val);
mp[val] = dq.begin();
return true;
}
int dequeue() {
if (dq.size() == 0) {
return -1;
}
int res = dq.back();
mp.erase(res);
dq.pop_back();
return res;
}
void erase(int val) {
auto it = mp.find(val);
if (it != mp.end()) {
dq.erase(it->second); // exception
mp.erase(val);
}
}
private:
deque<int> dq;
unordered_map<int, deque<int>::iterator> mp;
};
isn't std::deque a doubly linked list
No it is not. As stated in documentation
std::deque (double-ended queue) is an indexed sequence container that allows fast insertion and deletion at both its beginning and its end. In addition, insertion and deletion at either end of a deque never invalidates pointers or references to the rest of the elements.
emphasis is mine. Note that it says that pointer or references not invalidated, not iterators. And documentations on std::deque::push_front() clearly says so:
All iterators, including the past-the-end iterator, are invalidated. No references are invalidated.
As for the logic you are trying to implement I would recommend to use boost::multi_index as it allows single container with different access criteria and you do not have to maintain 2 containers in sync. Documentation can be found here

Set iterator invalidated by move semantic

I have the following immutable container class (public access to values is just for reasons of simplicity):
struct Container
{
std::unordered_set<int> values;
//Default constructor
Container() = default;
//Copy constructor
Container(const Container& other)
: values(other.values)
{ }
//Move constructor
Container(const Container&& other)
: values(std::move(other.values))
{ }
Container RemoveValue(int value) const
{
//Create a copy of this object
Container copy(*this);
//Remove one element from the copy
copy.values.erase(value);
return copy;
}
};
This container contains a set of values. The method RemoveValue() returns a copy of the current object where a specific value has been removed. An appropriate move constructor is defined for this struct.
I use this container as follows:
int main()
{
std::vector<Container> containers;
{
//Initialization
Container initialContainer;
initialContainer.values.insert(1);
initialContainer.values.insert(2);
containers.push_back(std::move(initialContainer));
}
const Container* currentContainer = &containers.front();
for (int value : currentContainer->values)
{
Container newContainer = currentContainer->RemoveValue(value);
//Do some checks, then...
containers.push_back(std::move(newContainer));
}
std::cout << containers.size() << std::endl;
return 0;
}
I initialize a vector of containers with a single container (with values 1 and 2). Then, I acquire a pointer to this initial element and iterate every value. For each value, I call RemoveValue() and insert the resulting container into the vector.
In gcc, this seems to work just fine. However, I get runtime errors in Visual Studio 2015.
In Debug Mode, the error is: "list iterator not incrementable". This error occurs after the first iteration at for (int value : currentContainer->values) (when the iterator is to be incremented).
In Release Mode, the error is: "Access violation reading at position 0x38". This error occurs at copy.values.erase(value) in RemoveValue. But only in the second iteration. Surprisingly, values does not contain elements at this point, anymore (size() returns 0).
I don't understand either of these errors. How can I resolve them?
A C++ Shell example also runs without errors. However, it outputs 2 as the final number of containers, whereas I expected three (the initial one, one with 1 removed, one with 2 removed).
currentContainer is a pointer to an element of the vector containers. The loop body modifies containers by calling push_back. That can invalidate pointers into the vector, and if it does, currentContainer can end up pointing to garbage.
In general, don't use pointers to objects that are held in an std::vector. Instead, use containers.front() or containers[0] to get at the first element.

Going over a vector (Segmentation fault)

I supose there is a problem with the iterators, but I can't understand why!
Could you please give me some more info?
I have the movie class with the following:
public:
vector<string> casting() const;
private:
string _titol;
short _year;
vector<string> _alies;
string _director;
vector<string> _casting;
The casting() method just returns the _casting vector
I have my main.cpp where I call:
void Movies::actorMovies(string actor){
bool existeix = false;
std::map<titleyear,Movie>::iterator it = _pelis.begin();
std::vector<string>::iterator it2;
for(it; it!=_pelis.end(); it++){
for(it2=it->second.casting().begin(); it2!=it->second.casting().end(); it2++){
/*if((*it2).compare(actor)==0){
cout<<"Titol: "<<it->first.t<<endl<<"Any: "<<it->first.y<<endl;
existeix = true;
}*/
}
}
if(!existeix)
cout<<"NOT FOUND"<<endl;
}
The Segmentation fault comes when i want Uncomment the if statement.
Can someone see what's happenning here?
Your premise is wrong. The casting() method returns a new copy of the _casting vector every time it is called. Thus it2 can never equal it->second.casting().end(), since it's an iterator to a completely different container!
In fact, it2 is immediately invalidated at the end of the full expression, since it's an iterator into a temporary container that dies immediately.
If casting() is meant to provide a view of the actual _casting vector, it should return an lvalue:
const std::vector<std::string> & casting() const { return _casting; }
// ^^^
The casting() function returns a temporary vector by value. Different calls to the function each return a new copy of the vector.
So when you write:
for(it2=it->second.casting().begin();
the it2 is immediately dangling because the temporary vector now gets destroyed.
The test it2!=it->second.casting().end(); compares a dangling iterator with the end of a new copy of _casting, causing undefined behaviour.
There are two options to fix this:
Store the result: vector<string> casting = it->second.casting(); for (it2 = casting.begin(); ..........
Make casting() return by reference, so that you will work on the actual instance of the vector inside it->second.

question about std::vector::end()

I recently finished fixing a bug in the following function, and the answer surprised me. I have the following function (written as it was before I found the bug):
void Level::getItemsAt(vector<item::Item>& vect, const Point& pt)
{
vector<itemPtr>::iterator it; // itemPtr is a typedef for a std::tr1::shared_ptr<item::Item>
for(it=items.begin(); it!=items.end(); ++it)
{
if((*it)->getPosition() == pt)
{
item::Item item(**it);
items.erase(it);
vect.push_back(item);
}
}
}
This function finds all Item objects in the 'items' vector that has a certain position, removes them from 'items', and puts them in 'vect'. Later, a function named putItemsAt does the opposite, and adds items to 'items'. The first time through, getItemsAt works fine. After putItemsAt is called, though, the for loop in getItemsAt will run off the end of 'items'. 'it' will point at an invalid Item pointer, and getPosition() segfaults. On a hunch, I changed it!=items.end() to it<items.end(), and it worked. Can anyone tell me why? Looking around SO suggests it might involve erase invalidating the iterator, but it still doesn't make sense why it would work the first time through.
I'm also curious because I plan to change 'items' from a vector to a list, since list's erase is more efficient. I know I'd have to use != for a list, as it doesn't have a < operator. Would I run into the same problem using a list?
When you call erase(), that iterator becomes invalidated. Since that is your loop iterator, calling the '++' operator on it after invalidating it is undefined behavor. erase() returns a new valid iterator that points to the next item in the vector. You need to use that new iterator from that point onwards in your loop, ie:
void Level::getItemsAt(vector<item::Item>& vect, const Point& pt)
{
vector<itemPtr>::iterator it = items.begin();
while( it != items.end() )
{
if( (*it)->getPosition() == pt )
{
item::Item item(**it);
it = items.erase(it);
vect.push_back(item);
}
else
++it;
}
}
You're invoking undefined behavior. All the iterators to a vector are invalidated by the fact that you called erase on that vector. It's perfectly valid for an implementation to do whatever it wants.
When you call items.erase(it);, it is now invalid. To conform to the standard, you must now assume that it is dead.
You invoke undefined behavior by using that invalid iterator in the next call to vect.push_back.
You invoke undefined behavior again by using it as the tracking variable of your for loop.
You can make your code valid by using std::remove_copy_if.
class ItemIsAtPoint : std::unary_function<bool, item::Item>
{
Point pt;
public:
ItemIsAtPoint(const Point& inPt) : pt(inPt) {}
bool operator()(const item::Item* input)
{
return input->GetPosition() == pt;
}
};
void Level::getItemsAt(vector<item::Item>& vect, const Point& pt)
{
std::size_t oldSize = items.size();
std::remove_copy_if(items.begin(), items.end(), std::back_inserter(vect),
ItemIsAtPoint(pt));
items.resize(vect.size() - (items.size() - oldSize));
}
You can make this a lot prettier if you are using boost::bind, but this works.
I'll go with Remy Lebeau's explanation about iterator invalidation, and just add that you can make your code valid and asymptotically faster (linear time, instead of quadratic time) by using a std::list instead of a std::vector. (std::list deletions only invalidate the iterator that was deleted, and insertions don't invalidate any iterators.)
You can also predictibly identify iterator invalidation while debugging by activating your STL implementation's debug mode. On GCC, you do with with the compiler flag -D_GLIBCXX_DEBUG (see some caveats there).