简体   繁体   中英

Copying STL: Removing Elements, User Defined Functions as Arguments and Event Queues

Copying the STL

The Standard Template Library includes many different types of container. All the containers are generic, as template arguments allow us to create containers of any type of object. I am writing a container wrapper class, and I am aiming to keep this constituency. I like to think of this as my first constraint.

Other constraints to note are: The container always contains events which are in priority order. (In the time-order in which they are due to occur.) Also, any particle which is associated with an event only appears in the event queue once. Picture a world with 3 particles, particle M, N and O. If particle M and N are detected to have an event in the future, and that event is in the event queue already, then neither particle M or particle N can be in the event queue associated with a different event . This means that if particle N and particle O are also detected to have an event, it will not be stored in the queue because particle N already had an event with particle M which is already in the queue. These two constraints don't really matter, but it might be useful for you to know about them.

Event Queues

To aid understanding, we will now take a detour into the background behind this question.

I am trying to implement something similar to the Priority Queue, except that I need to be able to access random elements and remove elements. Why I need to do this will be explained shortly.

I am writing a simulation which involves particles, and events which occur between pairs of particles at computable times in the future. Below is the code for the particle class.

// particle.hpp
class Particle
{
};

That's all there is to it. In another project I have there is a full implementation of particle. Also in this project is a function which implements an algorithm which detects events between pairs of particles and computes the time at which these events occur. I hope you appreciate I am trying to simplify everything as much as possible here.

So, numerous particles exist inside a vector, and a pair of particles may produce an event at a time in the future which can be calculated. Let us now take a look at the event class which holds key data about these events.

// event.hpp
class Event
{
    /* Event contains a variable to hold the time at which it occurs, and
     * two pointers which point to the two particles which are associated
     * with the event. */

    // Construct events with time and associated particles
    Event(double _time, Particle* _a, Particle* _b); 

    // Other Methods to return the time of the event, and pointers
    // to each of the particles involved in the event
    double time()
    {
        return m_time;
    }
    Particle* particleA()
    {
        return m_particle_a;
    }


    // Member data
    double m_time;
    Particle* m_particle_a, m_particle_b;
};

// Event needs an `operator<` for comparing event times, which I have not shown.

Okay so you can see how when events are detected between pairs of particles, the data about the time of the event and which particles are involved can be stored in this event class. Then multiple events are packed into a container similar to a priority queue which allows random access to elements and removal of any elements within the container. Remember that I am trying to copy the ideas from STL to make this container general for future use. This is where the problems begin to arise. Now let's look at the container class. (Brace for impact?)

// eventqueue.hpp
template<typename T> // Allow this container be a container of `Event`'s
class EventQueue
{
    void push(const T& _elem); // Function to insert element into correct position
                               // This requires use of the `operator<`, which compares
                               // `_elem < m_container[i]`
                               // and depending on the result of said comparison will
                               // insert the `_elem` into the correct place according
                               // to priority. (Remember `operator<` is overloaded to
                               // compare event times.

    // Below is the problem function.
    // This function is supposed to take a user defined function, `_func_p`, which
    // compares an element `_e`, which is also the argument to the function,
    // `_external_element`, and returns a boolean signifying if the element is to be
    // removed or not. I have included the implementation to show you in more detail.
    // The problem is obvious: `Event&` means this container is not general!
    // It will only work for the type `Event`
    void remove_if(bool (*_func_p)(T& _element, Event& _external_element), Event& _e)
    {
        for(int i = 0; i != m_container.size(); i ++)
        {
            if((*_func_p)(m_container[i], _e))
            {
                m_container.erase(m_container.begin() + i);
                break; // Pairs of events means at maximum, one item is to be removed.
            }
        }
    }

    const T& at(int _index); // Function to return element at position `_index`

    // Other functions not included here are functions to peak() top element
    // and pop elements. Also stuff like `size()` `empty()` and `clear()`.

    // Member data
    vector<T> m_container;
};

Okay, so that was pretty big, but the point is that the user should be able to define a function which makes a true or false decision about whether an event should be removed or not. To see why this is needed, see the Explanation of Events section.

Here is the function which makes that decision.

bool remove_if_event_is_invalidated(Event& _event_a, Event& _event_b)
{
    Particle* A = _event_a.particleA();
    Particle* B = _event_a.particleB();

    Particle* C = _event_b.particleA();
    Particle* D = _event_b.particleB();

    return (A == C || A == D || B == C || B == D) && (_event_a.time() < _event_b.time());
}

Essentially it checks to see whether any of the particles involved in the new event, _event_a are already involved in another event, _event_b . If you have read the final section already, then you will probably understand that in the case that "Condition 3" is met, then the newly created event invalidated the old event, and therefore the old one needs removing from the event que and the new one needs adding. "Condition 2" is that the new event does not invalidated any older events, because the new event occurs after any existing events involving the same particles.

An Explanation of Events

Once the event queue is populated with events, the highest priority event is processed. This is the event which occurs soonest. The event queue always contains events which are ordered according to the times at which they occur - in priority order.

When an event is processed, there are several things which could happen:

1:) Nothing happens: The event is processed and no new events are created as a result. Therefore nothing needs to be done.

2:) A new event is created between one particle involved in the event which has just been processed (call it Particle Y), and a different particle (call it Particle Z). However the new event occurs at a time after Particle Y is involved in yet another event with yet another particle (call it Particle U): As a result of this, Particle Y and Particle U interact before Particle Y would have interacted with Particle Z, and therefore the event between Y and U is likely to invalidate the event between Y and Z, so therefore we do nothing again .

3:) A new event is created, exactly as in 2:), however the newly created event occurs before another event in the event queue, and therefore invalidates the later event currently in the queue. This is the interesting one, because the invalidated event must be removed from the event queue, and the newly created event must be inserted into the correct position. This gives rise to the requirement for the operator< , which is a friendly function to class Event . This also has knock-on consequences, as if the newly created event occurs before many other events in the queue, the processing of that event may invalidate other events which occur at a later time, but that's not really important.

It isn't clear to me why _external_element and _e are of type Event & in the declaration of remove_if . It seems like you're expecting the container to be EventQueue<Event> - why can't you declare them as type T& like _element ?

If there's something I'm missing, it seems like another option would be to add a second template parameter to EventQueue . For example, if T isn't Event , but you still need to compare objects of type T to objects of type Event , and you want to do this generically, you could declare EventQueue as:

template <typename T, typename CompT>
class EventQueue
{
    void remove_if(bool (*_func_p)(T& _element, CompT& _external_element), CompT& _e);
    ...
};

If this isn't enough because there turn out to be several types that aren't T but are in some way related to T , that second parameter could be a traits type like you sometimes see in the Standard Library:

template <typename T, typename T_Traits>
class EventQueue
{
    void remove_if(bool (*_func_p)(T& _element, typename T_Traits::CompT& _external_element), typename T_Traits::CompT& _e);
};

struct Foo_Traits
{
    typedef Bar CompT;
    typedef Bar2 OtherT;
};

EventQueue<Foo, Foo_Traits> e;

This would let you add new related types as you discovered the need for them without adding a new template parameter every time. Of course, if one of the earlier options does the job, this is just overkill.

The root problem why it's not generic is the predicate use (*_func_p)(m_container[i], _e) in remove_if .

If you look at std::remove_if , it takes a generic Predicate p for which p(element) produces a boolean. That predicate captures all the necessary state.

You on the other hand pass _e separately. That is not needed. If the caller wanted to pass binary function F and use E as its second argument, he can pass std::bind(F, std::placeholders::_1, E)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM