我有一个使用std::priority_queue
的相当大的复杂算法。在某些情况下,我希望队列成为最小队列,而在其他情况下,我希望队列成为最大队列。所以我必须提供一个比较器类型,可以执行其中一个。由于比较器是模板参数,因此它基本上是std::priority_queue
类型的一部分。因此,在引用队列的任何地方,代码都必须知道类型。
当然,一个选项是提供一个具有状态的自定义比较器,并在operator()
的每次调用中在升序和降序之间进行选择,但我试图避免使用此分支的性能开销每一次比较。我能看到的另一个选择是复制整个算法,一个用于升序排序,另一个用于降序。
这个问题有更好的替代方案吗?
编辑:由于有些人似乎非常渴望在没有真正了解问题的情况下提供帮助,我会尝试详细说明一下。
我实现的是外部排序算法(外部合并排序)。该算法在合并阶段使用std::priority_queue
。现在,排序可以是升序或降序。对于这两种情况,std::priority_queue
比较器必须不同。你可以想象一个简单的排序算法可以很容易地参数化来处理升序/降序,如:
// psuedocode...
if (ascending) {
if (a > b) // do something
} else {
if (a < b) // do something
}
这样做的一个问题是必须检查ascending
标志每次迭代的排序循环。 'jalf'建议编译器可以“内联”这个分支,但我认为我没有看到它发生过,至少不是VC ++(是的,我看过一些程序集)。
现在,当使用std::priority_queue
以排序顺序保存某些内容时,队列的实际类型对于升序和降序排序是不同的(因为比较器必须不同,并且它是一个模板参数)。所以选项是:
operator()()
可以对升序和降序执行正确的比较。请注意我在算法期间不在两种不同的队列行为之间切换(正如一些人所要求的那样)。我在算法开始时将队列设置为最小或最大队列,并且对于该特定排序仍然如此。
现在比较上面的选项。
选项1因为不需要的代码重复和维护而被淘汰(尽管它可能会产生性能最佳的代码)。
选项2是不可取的,因为所有额外的运行时检查和分支,以及不必要的队列都不会被使用。
选项3更具吸引力,因为分支在比较器函数中被封装/隔离 - 但仍需要对每次比较进行运行时检查(这基本上是我想要避免的)。
选项4将问题提升一级,并要求调用者知道并可以访问比较器仿函数 - 有点乱。
选项5似乎相当不错 - 通过允许比较器功能在运行时不同而不改变队列的类型,它可以实现干净的代码。决策逻辑不必暴露给调用者(就像选项4一样)。
我可以在选项5中看到唯一的缺点是编译器可能无法通过函数指针内联调用 - 但在我的情况下它确实如此,因为调用了被调用的函数在同一个翻译单元中。如果它没有内联,我猜测选项3本来会更好,但在我的情况下,选项5的表现更好。
此外,在我意识到选项5是可能的(我之前没有)之后,我发现使用函数指针而不是函子可能没有做太多,我怀疑人们有时会跳过箍使用仿函数(正如我必须的那样),当一个函数指针可以使代码更清晰时(特别是当性能不是一个问题时)。
答案 0 :(得分:3)
使用模板
template <typename Comparator>
void algo()
{
std::priority_queue<int, std::vector<int>, Comparator> pqueue;
...
}
- 编辑
我看了你的编辑,但我仍然没有真正得到困扰你的东西。你说
Option 4 pushes the problem up one level and requires the
caller to know and have access to the comparator functors
-- kind of messy.
无论如何,来电者必须在升序与否之间做出选择。 当然,他没有必要知道使用哪种比较器类型:
void algo_ascending() {
algo<Ascending_Comparator>();
}
void algo_descending() {
algo<Descending_Comparator>();
}
甚至
void algo(bool ascending) {
if (ascending)
algo<Ascending_Comparator>();
else
algo<Descending_Comparator>();
}
答案 1 :(得分:1)
如果要使用具有状态的比较器,则无法更改该状态,因为比较器已被队列复制。为了在构造队列后更改比较器的状态,可以使用共享状态。
但是,在将元素添加到队列后,不应使用状态来更改比较结果。这可能会导致队列的奇怪行为。
struct shared_state_t
{
int number;
};
struct compare_t
{
bool operator <(const record_t &left, const record_t &right) const { left < right; }
tr1::shared_ptr<shared_state_t> shared;
};
void func()
{
tr1::shared_ptr<shared_state_t> state(new shared_state_t());
state->number = 42;
compare_t comp;
comp.shared = state;
std::priority_queue<record_t, vector<record_t>, compare_t> queue(comp);
// do something with queue
state->number = 999;
// compare object of queue is aware of the new state
}
答案 2 :(得分:0)
std::priority_queue
在其构造函数中使用比较器对象。将对象作为函数参数并将其传递给构造函数。每次调用都将根据参数创建不同的容器。所以这个分支只是在建设时。
template<class C>
void the_algorithm(const C& compare)
{
std::priority_queue<Type> q(compare);
// ...
}
bool this_before_that(const Type& op1, const Type& op2);
// ...
{
the_algorithm(this_before_that);
the_algorithm(ThatBeforeThis_Functor());
}
这里的分支计数仅仅是对算法的调用顺序。