Error in loop... problem with std::iterator?

Started by
4 comments, last by visitor 15 years, 5 months ago
I'm trying to write a message/event handler for my problem, but I'm having some problems getting it to behave like it should. It runs fine, but it doesn't clean up like it should. The event handler keeps a queue of events, using std::vector. Each event has three pieces of information: a numeric code representing the event, a string describing the event, and a numeric identifier to remember what part of the program added the event to the queue (which is what the clean up is based on). A part of the program starts by resetting the event handler to the first event in the queue, and then repeatedly calling the handler to get the next message, until the handler signals the end of the messages by returning NULL. As the handler goes through the messages in the queue, it cleans up by geting rid of messages that were added by the current part of the program.(Based on the assumption that every other part of the program has had a chance to read those messages.) And herein lays the problem: the message handler only deletes every other message that should be deleted. My guess is that it has to do with std::vector::iterator, or more specifically copying iterators. Here is the source file where the error should be. I'm pretty sure that it's in the function eventHandler::getEvent()
/*
 *  twiEventHandler.cpp
 */

#include <iostream>

#include "eventHandler.h"


eventHandler::eventHandler()
{
	currentSource = 0;
}



eventHandler::~eventHandler()
{
	// clear all variables
	myEvents.clear();
}



void eventHandler::addEvent(int code, std::string msg, int source)
{
	myEvents.push_back(event(code, msg, source));	// push the new message onto the back of the stack
}



void eventHandler::resetLoop(int id)
{
	currentEvent = myEvents.begin();// go to the front of the stack
	currentSource = id;		// find out who's calling (used for cleanup);
}



event* eventHandler::getEvent()
{
    // Current problem:
    // if there is are events from the current source, only every other event will be removed

    event* result = NULL;
    bool finished = false;  // flag that we found what we're looking for

	// loop until we get the next valid message, or we hit the end of the messages
	while (!finished)
	{
	    //  first see if we've reached the end of the message stack
	    if(currentEvent == myEvents.end())
	    {
	        // tell the loop to stop
            finished = true;
	    }
	    else    // still messages to go
	    {

//////////////////////////////////
// I'm pretty sure the problem is in the next few lines
//////////////////////////////////

// make a copy of the current position so that we don't loose our place if the current item gets deleted.
                prevEvent = currentEvent;
	        currentEvent++;

	        // we see if we should get rid of this message
	        if(prevEvent->source == currentSource)
	        {
				std::cout<<">>Removing message "<<prevEvent->code<<": "<<prevEvent->message<<" from module "<<prevEvent->source<<std::endl;
	            myEvents.erase(prevEvent);
	        }
	        else    // if we don't get rid of it
	        {
	            // set the current message to return
	            result = &(*prevEvent);
	            // and tell the loop that we found what we're looking for
	            finished = true;
	        }
	    }
	}
	// go to the next message
	return result;
}



void eventHandler::clear()
{
	myEvents.clear();	// get rid of al messages
} 
/*
 *  eventHandler.h
 */

#ifndef EVENT_HANDLER_H
#define EVENT_HANDLER_H

#include <string>
#include <map>
#include <vector>

// a container to hold all the parts of the message
typedef struct event
{
	event()	{}
	event(int codeIn, std::string msgIn, int sourceIn)
	{
		code = codeIn;
		message = msgIn;
		source = sourceIn;
	}

	int code;	// numerical code representing the message
	std::string message;	// human readable version of the message, mey have extra data
	int source;	// where the message came from, used for clean up
};

class eventHandler
{
public:
	eventHandler();
	~eventHandler();

	void addEvent(int code, std::string msg, int source);
	void resetLoop(int id);		// go back the the first message, get what part of the program is calling (for cleanup)
	event* getEvent();		// get the current message, then go to the next one
	void clear();			// get rid of all messages

private:
	std::vector<event>::iterator currentEvent;
	std::vector<event>::iterator prevEvent;
	int currentSource;
	std::vector<event> myEvents;
};


#endif	//EVENT_HANDLER_H 
main.cpp
#include <iostream>
#include "eventHandler.h"

eventHandler myEvents;

void readEvents(int module)
{
    std::cout<<"Running Module "<<module<<std::endl;
    myEvents.resetLoop(module);

    bool finished = false;
    event* temp = NULL;
    int eventCounter = 0;

    while (!finished)
    {
        temp = myEvents.getEvent();
        if(!temp)
        {
            finished = true;
        }
        else
        {
            eventCounter++;
            std::cout<<"Event: "<<temp->code<<":"<<temp->message<<" (module "<<temp->source<<")"<<std::endl;
        }
    }
    std::cout<<"Finished running module "<<module<<", "<<eventCounter<<" events"<<std::endl<<std::endl;
}

int main(int argc, char** argv)
{
    readEvents(1);

    myEvents.addEvent(1,"First event, first Module",1);
    myEvents.addEvent(2,"Second Event, first Module", 1);
    myEvents.addEvent(3, "Third Event, first Module", 1);
    myEvents.addEvent(4, "Fourth Event, first Module", 1);

    readEvents(3);
    readEvents(1);
    readEvents(3);
    return 0;
}
Thanks for taking the time to look at this.
Advertisement
Vector iterators are invalidated each time you add or remove the elements.

A much simpler and more natural way is to do something like this:
std::stack<event> events;...events.push(...);events.push(...);events.push(...);...while (!events.empty()) {  switch (events.top().code) {    case ...  }  events.pop();}


The reason for this lies in memory management. You're trying to workaround returning a copy by providing a pointer, but that complicates things too much.

I have no clue what the event source is supposed to do.
I was hoping to be able to delete items from the middle of the queue without interrupting the cycle.
The purpose of the event source is to keep track of what part of the program sets an event, and then delete that event again the next time that that part of the program accesses the event queue. This way, old messages get cleaned up after every other part of the program has had a chance to read them.

I'm thinking that I might just have to move the cleanup into the function where the loop gets reset...
Quote:Original post by methinks
I was hoping to be able to delete items from the middle of the queue without interrupting the cycle.
If you need to delete items from the middle on a regular basis, then a std::vector is the wrong data structure. Perhaps a linked list makes more sense in your case?

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

I don't necessarily agree with how you're designing your event queue, but at any rate you need to be extra careful when iterating through a container that can be modified during the iteration. Vectors are especially nasty, since modifying a vector can potentially invalidate every iterator (and you should always assume it does). A typical iterate-with-erase loop might look like this:
std::vector<int> data;// add a lot of stuff to datafor(std::vector<int>::iterator it = data.begin(); it != data.end(); /* no increment! */){   if(Should_Erase(*it))      it = data.erase(it);   else      ++it;}

Notice how we either increment the iterator normally, or let the 'erase' function tell us where the next valid iterator is.
Because vectors invalidate iterators, it is also not a good idea to keep iterators around for longer (or you need more code to keep them updated, e.g here):

void eventHandler::addEvent(int code, std::string msg, int source){	myEvents.push_back(event(code, msg, source));	// push the new message onto the back of the stack}

This fixes it:

void eventHandler::addEvent(int code, std::string msg, int source){    //get index from iterators    unsigned current = currentEvent - myEvents.begin();    unsigned previous = prevEvent - myEvents.begin();    myEvents.push_back(event(code, msg, source));	// push the new message onto the back of the stack    //fix iterators    currentEvent = myEvents.begin() + current;    prevEvent = myEvents.begin() + previous;  }


Of course, in this respect, indices would stay valid and you could just push back events again.

This topic is closed to new replies.

Advertisement