复制STL:删除元素、用户定义函数作为参数和事件队列

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

本文关键字:函数 参数 事件队列 定义 用户 STL 删除 元素 复制      更新时间:2023-10-16

复制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;
};

好吧,所以这是相当大的,但关键是用户应该能够定义一个函数来做出truefalse决定是否应该删除一个事件。要了解为什么需要这样做,请参阅事件解释部分。

这是做出这个决定的函数。

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的友好函数。这也会产生连锁反应,因为如果新创建的事件发生在队列中的许多其他事件之前,那么该事件的处理可能会使稍后发生的其他事件无效,但这并不重要。

我不清楚为什么_external_element_eremove_if的声明中是Event &类型。似乎你期望容器是EventQueue<Event>-为什么你不能像_element一样将它们声明为T&类型?

如果我错过了什么,似乎另一个选择是添加第二个模板参数到EventQueue。例如,如果T不是Event,但是您仍然需要比较类型为T的对象和类型为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;

这将允许您在发现需要时添加新的相关类型,而不必每次都添加新的模板参数。当然,如果前面的选项之一可以完成任务,那么这只是多余的。

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

如果你看std::remove_if,它接受一个通用的Predicate p,p(element)产生一个布尔值。该谓词捕获所有必要的状态。

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