1

复制 STL

标准模板库包括许多不同类型的容器。所有容器都是通用的,因为模板参数允许我们创建任何类型对象的容器。我正在编写一个容器包装类,我的目标是保留这个选区。我喜欢将此视为我的第一个约束。

需要注意的其他限制是: 容器始终包含按优先级顺序排列的事件。(按照它们应该发生的时间顺序。)此外,与事件相关联的任何粒子仅在事件队列中出现一次。想象一个有 3 个粒子的世界,粒子 M、N 和 O。如果检测到粒子 M 和 N 在未来有一个事件,并且该事件已经在事件队列中,那么粒子 M 和粒子 N 都不会在与不同事件关联的事件队列。这意味着如果粒子 N 和粒子 O 也被检测到有一个事件,它不会存储在队列中,因为粒子 N 已经与粒子 M 发生了一个事件,该事件已经在队列中。这两个约束并不重要,但了解它们可能对您有用。

事件队列

为了帮助理解,我们现在将绕道这个问题背后的背景。

我正在尝试实现类似于优先队列的东西,除了我需要能够访问随机元素和删除元素。为什么我需要这样做,稍后会解释。

我正在编写一个涉及粒子的模拟,以及在未来可计算时间发生在粒子对之间的事件。下面是粒子类的代码。

// particle.hpp
class Particle
{
};

这里的所有都是它的。在另一个项目中,我有一个完整的粒子实现。在这个项目中还有一个函数,它实现了一种算法,该算法检测粒子对之间的事件并计算这些事件发生的时间。我希望你能欣赏我在这里尽可能地简化一切。

所以,一个向量中存在着无数个粒子,一对粒子可能会在未来的某个时间产生一个事件,这个事件是可以计算的。现在让我们看一下包含有关这些事件的关键数据的事件类。

// 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.

好的,因此您可以看到如何在粒子对之间检测到事件时,有关事件时间以及涉及哪些粒子的数据可以存储在此事件类中。然后将多个事件打包到一个类似于优先级队列的容器中,该队列允许随机访问元素并删除容器内的任何元素。请记住,我正在尝试复制 STL 的想法,以使该容器通用以供将来使用。这就是问题开始出现的地方。现在让我们看一下容器类。(为冲击做好准备?)

// 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;
};

好的,这是相当大的,但重点是用户应该能够定义一个函数来true决定false是否应该删除一个事件。要了解为什么需要这样做,请参阅事件说明部分。

这是做出该决定的功能。

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());
}

本质上,它检查是否有任何参与新事件的粒子_event_a已经参与了另一个事件_event_b。如果您已经阅读了最后一节,那么您可能会明白,在满足“条件 3”的情况下,新创建的事件会使旧事件无效,因此旧的事件需要从事件队列中移除,新的事件一个需要添加。“条件2”是新事件不会使任何旧事件无效,因为新事件发生在任何现有事件涉及相同粒子之后。

事件解释

一旦事件队列被事件填充,最高优先级的事件就会被处理。这是最快发生的事件。事件队列始终包含根据事件发生的时间排序的事件 - 以优先级顺序。

处理事件时,可能会发生几件事:

1:) 什么都没有发生:事件被处理,结果没有新的事件被创建。因此,无需做任何事情。

2:) 在刚刚处理的事件中涉及的一个粒子(称为粒子 Y)和另一个粒子(称为粒子 Z)之间创建一个新事件。然而,新事件发生在粒子 Y 与另一个粒子(称为粒子 U)参与另一个事件之后的时间:因此,粒子 Y 和粒子 U 在粒子 Y 与粒子 Z 相互作用之前相互作用,因此 Y 和 U 之间的事件很可能使 Y 和 Z 之间的事件无效,因此我们不再做任何事情

3:) 创建了一个新事件,与 2:) 完全相同,但是新创建的事件发生在事件队列中的另一个事件之前,因此使队列中的当前事件无效。这很有趣,因为失效的事件必须从事件队列中移除,而新创建的事件必须插入到正确的位置。这就产生了对 的要求operator<,这是对 的友好函数class Event。这也有连锁反应,好像新创建的事件发生在队列中的许多其他事件之前,该事件的处理可能会使稍后发生的其他事件无效,但这并不重要。

4

2 回答 2

0

我不清楚为什么_external_element并且_eEvent &. remove_if似乎您期望容器是EventQueue<Event>- 为什么不能将它们声明为 type T&like _element

如果我缺少某些东西,似乎另一种选择是将第二个模板参数添加到EventQueue. 例如, if Tis not Event,但您仍然需要将 typeT对象与 type 对象进行比较Event,并且您希望一般地执行此操作,您可以声明EventQueue为:

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

如果这还不够,因为有几种类型不存在T但在某种程度上与 相关T,那么第二个参数可能是您有时在标准库中看到的特征类型:

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;

这将允许您在发现需要时添加新的相关类型,而无需每次都添加新的模板参数。当然,如果较早的选项之一可以完成工作,那只是矫枉过正。

于 2013-08-29T06:20:25.807 回答
0

为什么它不是通用的根本问题是谓词(*_func_p)(m_container[i], _e)remove_if.

如果您查看,它需要一个生成布尔值std::remove_if的泛型。该谓词捕获所有必要的状态。Predicate pp(element)

另一方面,您_e分别通过。那是不需要的。如果调用者想要传递二进制函数 F 并使用 E 作为它的第二个参数,他可以传递std::bind(F, std::placeholders::_1, E)

于 2013-09-03T09:33:46.157 回答