I'm coding a program where I want to draw a card, and then delete so that it doesn't get drawn again.
I have a vector of Cards (class containing 2 structs that define Suit and Value) called deck and I don't really know how to use iterators very well, here a code snippet:
void Player::discardCard(CardDeck masterDeck)
{
cout << "Erasing: " << masterDeck.getDeck().at(cardSelect).toString() << endl;
/*Attempt1*/
masterDeck.getDeck().erase(masterDeck.getDeck().begin()+cardSelect);
/*Attempt 2*/
vector<Card>::iterator itr;
itr = masterDeck.getDeck().begin() + cardSelect;
masterDeck.getDeck().erase(itr);
}
cardSelect has the location of the card I'm going to delete.
It's generated randomly within the boundaries of 0 and the size of deck; therefore it shouldn't be pointing to a position out of boundaries.
Everytime I compile I get the following error:
"Expression: vector erase iterator outside range"
I really don't know what to do, hopefully someonw can help me, thanks in advance!
My bet is that getDeck returns the vector by value. It causes itr to point to and erase to operate on different copies of the vector. Thus you get the error. You should return the vector by reference. Change getDeck signature to this one:
vector<Card>& getDeck()
Let me go off topic first. Your design is a little suspect. First passing in CardDeck by value is almost certainly not what you want but that's even beside the point. Why should your Player class have all this inside knowledge about the private innards of CardDeck. It shouldn't care that you store the deck as a vector or deque (ha ha), or what the structure is. It just shouldn't know that. All it knows is it wants to discard a card.
masterDeck.Discard(selectedCard);
Also note that selectedCard has to be between 0 and ONE LESS than the size of the deck, but even that's probably not your problem (although it will be 1/53rd of the time)
So to answer your question we really would need to now a little more about masterDeck. Did you implement a valid custom copy constructor? Since you're passing by value odds are good you're not correctly copying the underlying vector, in fact it's probably empty and none of the deletes will work. Try checking the size. If you don't ever want the deck copied then you can let the compiler help you by declaring a private copy constructor and then never defining it. See Scott Meyer's Effective C++ Item 11.
Finally one last piece of advice, I believe once you erase with your iterator, you invalidate it. The vector might get reallocated (almost certainly will if you erase anywhere but the end). I'm just telling you so that you don't try to call erase more than once on the same iterator. One of the tricky things about iterators is how easy it can be to invalidate them, which is why you often seen checks for iter != coll.end().
"It's generated randomly within the boundaries of 0 and the size of deck".
The valid range should be "between 0 and the size of the deck minus 1". This could generate range error at run time.
Related
First off, good morning/day/evening and thank you to whoever is taking their time to read this.
The Setup:
In my code I have two different classes: ColObj and QuadNode (that is, the 'collision object' and a node in a quad tree used to check for proximity of objects. I know there are probably libraries out there for this, but I need to write my own system, so they would be of no use here). Things work like this: When a ColObj object is created, it is added into an appropriate QuadNode (the node has a std::list of pointers to ColObj), so that the node can inform it when it collides with something; the ColObj object also receives a pointer to the node that's holding it and a list iterator to the list iterator containing its address so when it's out of the node's bounds or gets destroyed it can 'leave' it, and clean up the node, that is, remove and reference to itself from the node. I made it like this because in a lot of cases it's going to be a frequent operation and I want it to be in constant time.
The Code:
This is the method used to 'attach' a ColObj to a QuadNode. I suspect the problem is not in here.
void QuadNode::obj_add(ColObj *obj) {
std::cout<<"QuadNode at depth ("<<depth<<") received new ColObj.\n";
objects.push_back(obj);
obj->holder = this;
obj->my_iter = std::prev( objects.end() );
if ((int)objects.size() > MAX_OBJECTS && depth < MAX_DEPTH) split();
}
This is the QuadNode method that a ColObj uses to clean up the node. Here is where the problem occures for some reason.
void QuadNode::obj_list_erase(std::list<ColObj*>::iterator iter) {
std::list<ColObj*>::iterator iter2 = objects.begin();
objects.erase(iter);
}
The first line in this method is simply to provide additional information for debugging and will be removed afterwards.
The Error:
The strangest part is that, for the most part, the code works fine. Then at one point, randomly, it throws an assertion failure, saying that a "list iterator is not incrementable". That's the first strange thing, I'm not trying to increment it anywhere in my code (though I know that std::list::erase returns the following iterator, but I never attempt this operation on an invalid or "past-the-last" iterator).
Anyway, Visual Studio offers to fire up the debugger and put a break point in the code, so naturally I agree. So here's the weirdest part:
Local and auto variables, debugger screenshot
(I can't embed an image since I'm new here, so it is what it is).
So, unless I'm gravely mistaken here, it's telling me that the passed iterator is equal to be beginning iterator of the list, that its element is still present in the list and corresponds to the first (or rather zero-th) element of the list. And yet, the erase() method fails.
For what it's worth, I've noticed that every single time the program breaks, the passed iterator points to the zero-th element of the list, though I can confirm that the method usually works even when there's only one element in the list.
Additional info and conclusion:
I'm not manually incrementing the iterator anywhere else in the code (which is pretty small and simple anyway).
The IDE I'm using is Visual Studio Community 2015, but I don't know the compiler version. (Microsoft and their naming schemes...)
I tried finding another thread about this on SO but every one I checked was about wrongly placed i++ in list iterations, so sorry if this is a duplicate thread.
I'm completely confused by this problem, because usually between the excellent debugger, std::cout and browsing SO I somehow fix the issue, but this time around nothing useful is coming up, so any advice or suggestion would be very welcome.
Edit:
One thing I have tried "just 'cause" editing the QuadNode::obj_list_erase method so that it compares the passed iterator with the first iterator of its list (objects.begin()) and if they're equal use objects.pop() to remove it, else erase it normally. It didn't work, saying that the iterators weren't compatible, whatever that means...
After finding out that I cannot even compare the passed iterator with any other iterator from the list that was supposed to be holding it (I was getting Assertion failure: iterators not compatible), I searched SO for more info on what it means, and... Andrew Kashpur was right. I did manage to invalidate an iterator by removing the pointed element from the list and putting it back immediately, but without updating the iterator.
Moral of the story: An iterator can seem to point to a "correct" memory location, it may even point to the same address as some valid iterator does, but that does not make it valid or compatible.
I am to write a C++ program that :
"Implements the vector ADT by means of an extendable array used in a circular fashion, so that insertions and deletions at the beginning and end run in constant time. (So not O(n)). Print the circular array before and after each insertion and deletion, You cannot use the STL."
This task seems very confusing to me. A std::vector is implemented using a dynamic array that is based off the concept of a stack, correct? Performing a deletion or insertion at the front seems to me that this should be implemented as a Queue or maybe a Dequeue, not a Vector. Also, a circular array would mean that when data is pushed onto an array that is Full, old data becomes overwritten, right? So when should I know to expand the vector's capacity?
If I'm not making sense here, Basically I need help in understanding how I should go about implementing a dynamic circular array..
Yes, this is a homework assignment. No, I do not expect anyone to provide code for me, I only wish for someone to give me a push in the right direction as to how I should think about implementing this. Thank you.
I think you are actually being asked to implement deque. The point of the "circularity" is that in normal vector you cannot add an element at the beginning since there is no free space and you would have to move all other elements to the right. So what you can do is you simulate a circle by putting the element to the end the base array and remember that's where the first element is.
Example: 2, 3, -, -, 1 where 1 is first and 3 is last
So, basically you insert elements circullary, and remember where the first and the last elements are so you can add to beginning/end in O(1). Also when the array is full, you have to move all the elements to a larger one. If you double the size, you still get amortized time of O(1)
1) m_nextIn and m_nextOut - data attributes of class queue;
I find it useful to have two integers, with label m_nextIn and m_nextOut ... these identify where in the circular array you 'insert' the next (i.e. youngest) obj instance into the queue, and where you 'delete' the oldest obj instance from the queue.
These two items also provide constant time insert and delete.
Don't get confused as to where the beginning or end of the queue is. The array starts at index 0, but this is not the beginning of your queue.
The beginning of your queue is at nextIn (which probably is not 0, but may be). Technique also known as round-robin (a research term).
2) empty and full - method attributes
Determining queue full / empty can be easily computed from m_nextIn and m_nextOut.
3) extendable
Since you are prohibited from using vector (which itself is extendable) you must implement this functionality yourself.
Note about your comment: The "dynamic memory" concept is not related to stack. (another research term)
Extendable issues occur when your user code invokes the 'insert' AND the array is already full. (capture this test effort) You will need to detect this issue, then do 4 things:
3.1) allocate a new array (use new, and simply pick an appropriate size.)
Hint - std::vector() doubles it's capacity each time a push_back() would overflow the current capacity
3.2) transfer the entire contents of the array to the new array, fixing all the index's as you go. Since the new array is bigger, just insert trivially.
3.3) delete the old array - i.e. you copied from the old array to the new array, so do you 'delete' them? or simply delete the array?
3.4) finish the 'insert' - you were in the middle of inserting another instance, right?
Good luck.
I have 3 scenarios with c++ iterator which all together confused me.
Here's my main code:
int arr[] = {13,20,40};
set<int> st(arr,arr+3);
auto it=st.begin();
auto tmp=it;
it++;
st.erase(it);
//scenarios here
1- if I write the following, result is 20, why?
cout << *tmp << endl;
2- If I move pointer forward, result is 13, why?
tmp++;
cout << *tmp << endl;
3- If I move pointer backward, result is also 13, why it's same as moving forward?
tmp--;
cout << *tmp << endl;
4- Finally, if instead of erasing something from middle of set, I erase the start item, the result is a random number.
auto it=st.begin();
auto tmp = it;
st.erase(it);
cout << *tmp << endl;//result: 13
tmp++;
cout << *tmp << endl;//result: random number
If you know any useful link about iterators in c++ related to this issue, please mention them.
If you delete the entry in a set that an iterator points to, you have invalidated the iterator. Once an iterator has been invalidated, you should no longer user it, as it causes undefined behavior.
First you don't mention your compiler. In a question like "how do I get C++ to do X,Y and Z" that is OK, but in a question like this where you ask why your program does not do what you expect you should specify compiler ( and version! ).
The reason is simple. First there may be a compiler bug which may cause certain code to not do what it is supposed to do. Second there are loose areas of the standard. For example in this case, there is no mention of how set is implemented, just how the interface is supposed to behave. So set can be implemented as a tree, an array, a hash table, whatever. Those behaviours change how set acts especially in "undefined" cases.
Second when you show multiple test cases, like here. it would be useful to separate the test cases with macros.
#ifdef TEST1
cout << *tmp << endl;
#endif
#ifdef TEST2
tmp++;
cout << *tmp << endl;
#endif
#ifdef TEST3
tmp--;
cout << *tmp << endl;
#endif
Generally you should avoid macros, but conditional compilation is one of the few exceptions to that rule.
So now on to your question.
First. I tend to use mostly vector, I tend to thing of iterators as C style pointers and ++ and -- as pointer arithmetic. It's hard to see how that is not the case for vector. but it is not always the case. It kind of messes with my instincts. Nevertheless it is a good picture to have in your head.
Just keep in mind that It's only mostly accurate. Depending on how set is implemented iterators will be different. In the case of a tree the iterator will be a pointer and ++ will be this->next and -- will be this->last.
Second. There are two types of collections. Collections ( which I will call basic collections ) which are based on "primitive" datatypes, and virtual collections which are things like directory listings or SQL Result Sets. In the first case, there is almost always a pointer hidden somewhere in the bowels of the iterator ( or not so deep ). Even in the second case, there is some sort of pointer like object ( a file descriptor/handle, a SQL Cursor ). So despite the fact that an iterator is put in an undefined state, it will still point to something. Though that thing might be invalid. It's like a character pointer which points to the wrong spot. There are still characters there, they just might be junk.
Third. To understand why iterators are made inconsistent when a collection changes, think about what you would have to do to create an iterator that remains consistent after a container change? First you would need an observer pattern implemented in the container and the iterator to let the iterator know when the container gets changed. An iterator is supposed to be a light weight object. This alone would cause at a doubling of iterator size. The message passing would add overhead when iterators are created.
The standards committee has a rule: any feature that you don't use should not have overhead when not used. That would mean you would need separate classes of at least a way to distinguish whether a container allows iterators that are consistent when the containers change. That would mean an extra level of complexity in the STL.
There is also the problem of how to fix an inconsistent iterator. Assume that the element pointed to by the iterator is deleted. What do you do? Go back one? Go forward one? What if the collection is unordered? Then changing the collection can cause the iteration order to be changed. How do you make sure that the iteratior hits all nodes?
Fourth. When doing something like this ( a teaching program ), it's a good idea to step through the code with a debugger to get an idea of what's going on inside.
Sorry it took so long. But the anwer was more complicated then I thought. That is not new for C++. Welcome to it.
[SOLVED]
So I decided to try and create a sorted doubly linked skip list...
I'm pretty sure I have a good grasp of how it works. When you insert x the program searches the base list for the appropriate place to put x (since it is sorted), (conceptually) flips a coin, and if the "coin" lands on a then that element is added to the list above it(or a new list is created with element in it), linked to the element below it, and the coin is flipped again, etc. If the "coin" lands on b at anytime then the insertion is over. You must also have a -infinite stored in every list as the starting point so that it isn't possible to insert a value that is less than the starting point (meaning that it could never be found.)
To search for x, you start at the "top-left" (highest list lowest value) and "move right" to the next element. If the value is less than x than you continue to the next element, etc. until you have "gone too far" and the value is greater than x. In this case you go back to the last element and move down a level, continuing this chain until you either find x or x is never found.
To delete x you simply search x and delete it every time it comes up in the lists.
For now, I'm simply going to make a skip list that stores numbers. I don't think there is anything in the STL that can assist me, so I will need to create a class List that holds an integer value and has member functions, search, delete, and insert.
The problem I'm having is dealing with links. I'm pretty sure I could create a class to handle the "horizontal" links with a pointer to the previous element and the element in front, but I'm not sure how to deal with the "vertical" links (point to corresponding element in other list?)
If any of my logic is flawed please tell me, but my main questions are:
How to deal with vertical links and whether my link idea is correct
Now that I read my class List idea I'm thinking that a List should hold a vector of integers rather than a single integer. In fact I'm pretty positive, but would just like some validation.
I'm assuming the coin flip would simply call int function where rand()%2 returns a value of 0 or 1 and if it's 0 then a the value "levels up" and if it's 0 then the insert is over. Is this incorrect?
How to store a value similar to -infinite?
Edit: I've started writing some code and am considering how to handle the List constructor....I'm guessing that on its construction, the "-infinite" value should be stored in the vectorname[0] element and I can just call insert on it after its creation to put the x in the appropriate place.
http://msdn.microsoft.com/en-us/library/ms379573(VS.80).aspx#datastructures20_4_topic4
http://igoro.com/archive/skip-lists-are-fascinating/
The above skip lists are implemented in C#, but can work out a c++ implementation using that code.
Just store 2 pointers. One called above, and one called below in your node class.
Not sure what you mean.
According to wikipedia you can also do a geometric distribution. I'm not sure if the type of distribution matters for totally random access, but it obviously matters if you know your access pattern.
I am unsure of what you mean by this. You can represent something like that with floating point numbers.
You're making "vertical" and "horizontal" too complicated. They are all just pointers. The little boxes you draw on paper with lines on them are just to help visualize something when thinking about them. You could call a pointer "elephant" and it would go to the next node if you wanted it to.
eg. a "next" and "prev" pointer are the exact same as a "above"/"below" pointer.
Anyway, good luck with your homework. I got the same homework once in my data structures class.
If I change an element of an std::set, for example, through an iterator, I know it is not "reinserted" or "resorted", but is there any mention of if it triggers undefined behavior? For example, I would imagine insertions would screw up. Is there any mention of specifically what happens?
You should not edit the values stored in the set directly. I copied this from MSDN documentation which is somewhat authoritative:
The STL container class set is used
for the storage and retrieval of data
from a collection in which the values
of the elements contained are unique
and serve as the key values according
to which the data is automatically
ordered. The value of an element in a
set may not be changed directly.
Instead, you must delete old values
and insert elements with new values.
Why this is is pretty easy to understand. The set implementation will have no way of knowing you have modified the value behind its back. The normal implementation is a red-black tree. Having changed the value, the position in the tree for that instance will be wrong. You would expect to see all manner of wrong behaviour, such as exists queries returning the wrong result on account of the search going down the wrong branch of the tree.
The precise answer is platform dependant but as a general rule, a "key" (the stuff you put in a set or the first type of a map) is suppose to be "immutable". To put it simply, that should not be modified, and there is no such thing as automatic re-insertion.
More precisely, the member variables used for to compare the key must not be modified.
Windows vc compiler is quite flexible (tested with VC8) and this code compile:
// creation
std::set<int> toto;
toto.insert(4);
toto.insert(40);
toto.insert(25);
// bad modif
(*toto.begin())=100;
// output
for(std::set<int>::iterator it = toto.begin(); it != toto.end(); ++it)
{
std::cout<<*it<<" ";
}
std::cout<<std::endl;
The output is 100 25 40, which is obviously not sorted... Bad...
Still, such behavior is useful when you want to modify data not participating in the operator <. But you better know what you're doing: that's the price you get for being too flexible.
Some might prefer gcc behavior (tested with 3.4.4) which gives the error "assignment of read-only location". You can work around it with a const_cast:
const_cast<int&>(*toto.begin())=100;
That's now compiling on gcc as well, same output: 100 25 40.
But at least, doing so will probably makes you wonder what's happening, then go to stack overflow and see this thread :-)
You cannot do this; they are const. There exists no method by which the set can detect you making a change to the internal element, and as a result you cannot do so. Instead, you have to remove and reinsert the element. If you are using elements that are expensive to copy, you may have to switch to using pointers and custom comparators (or switch to a C++1x compiler that supports rvalue references, which would make things a whole lot nicer).