C++ std::set 使用基于时间的比较器

C++ std::set with a comparator that is time based?

本文关键字:时间 比较器 set std C++ 于时间      更新时间:2023-10-16

在 C++std::set 中使用的比较器依赖于时间是否不好?即比较器一次返回一件事,但即使对于完全相同的对象也可能在不同的时间返回另一个结果?

我读过 std::sets 在给定比较器的情况下是弱排序的。也许有一种方法可以在获取mySet.begin()myset.end()值之前刷新顺序?

作为可能随时间变化的比较器的示例,请考虑事件并希望确定更接近当前时间的事件的优先级:

class Event {
public:
    int64_t nTime;
}
class EventComparator {
    // pa < pb
    bool operator()(Event* pa, Event* pb) {
        int64_t now = GetTime();
        return (std::abs(pa->nTime - now) > std::abs(pb->nTime - now));
    }
}

也许有更好的方法按时间对事件进行排序?显然,我可以只做一个向量并循环找到最大的事件,但只是认为使用内置插槽作为比较器会很好。

如果std::set中元素的相对顺序发生变化,则结果是未定义的行为。

就你而言,只要事情永远不会在未来发生,时间永远不会溢出,-now就毫无意义。 如果在任何时候在当前时间之前和之后发生事情,您的代码将表现出未定义的行为。

如果你只是简单地按时间对所有内容进行排序,然后寻找现在附近的元素,并从那里进行处理,你会得到类似的结果,但没有未定义的行为。

即,有一张从int64_tEvent*的地图。 将时间戳存储在int64_t中(这使某些事情变得更容易)。 然后获取当前时间lower_bound,并仔细检查它周围,以找到甚至"最近"到现在(或您选择的任何窗口)。

如果您的编译器支持透明比较器(C++14 功能),则可以使用 std::set 执行此操作。 只需在Event*int64_t时间戳之间实现一个透明的比较器。

如果我

弄错了,C++大师纠正我:

我相信std::set实例使用的比较器对底层容器内集合中包含的元素进行排序。这对于最大程度地降低 std::set 定义的操作和功能的复杂性是必要的。由于集合依赖于这种排序,因此更改顺序是非常不明智的 - 它会导致间歇性(有时顺序可能仍然是正确的),依赖于集合元素的正确排序的集合函数中的未定义行为。

在没有看到您的代码的情况下冒险猜测,我会说std::liststd::vector可能更适合您的应用程序。检查您将使用的操作和函数的复杂性,并确保所有这些操作和函数都存在于所选容器上。您可能需要依靠 std::algorithm 类来获得某些任务的帮助,但如果可能,请尝试坚持使用容器的内置功能。

我大体上同意 Yakk 和 Conduit 所说的话:认真地说,不要让你的比较器依赖于全局状态。 为了更权威地支持这一点,Herb Sutter和Andrei Alexandrescu的C++编码标准中的第87项说:

使谓词成为纯函数。

谓词纯度:谓词是返回是/否答案的函数对象,通常作为bool值。 如果一个函数的结果仅取决于其参数,那么它的结果在数学意义上是纯函数(请注意,这种"纯"的使用与纯虚函数无关)。

不允许谓词保存或访问影响其operator()结果的状态,包括成员状态和全局状态。 更喜欢使operator()成为谓词的const成员函数。

我最想添加到讨论中的是一个简单的解决方案,以实现我认为应该首选的多个排序。

  1. 将所有元素(类型为 Foo)存储在std::vector<Foo>中,使用永不更改的任意顺序。 显而易见的选择当然是在它们发生时简单地push_back()它们。
  2. 对于您希望使用的每个排序,定义一个纯排序函子Compare,该函子具有一个bool operator()(const Foo *, const Foo *) const;和一个相应的std::set<Foo *, Compare>(使用接受两个迭代器和一个比较器的构造函数),该是指向向量的指针的轻量级结构。
  3. 如果订单过时,只需处理相应的集合即可。

仅当您打算访问每个排序多次(按元素数量的顺序)次时,此方法才有价值。 如果您只想查找少量元素以进行各种排序,请考虑改用std::partial_sortstd::partial_sort_copy。 请考虑对复制成本低的指针进行排序std::vector<Foo *>而不是原始Foo对象。

现在对于一些代码:

#include <algorithm>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <set>
#include <string>
#include <utility>
#include <vector>
using Foo = std::pair<float, float>;
using FooIter = std::vector<Foo>::iterator;
std::ostream&
operator<<(std::ostream& os, const Foo& foo)
{
  os << std::fixed << std::setprecision(4)
     << "["
     << std::setw(8) << foo.first
     << ", "
     << std::setw(8) << foo.second
     << "]";
  return os;
}
// Ugly template, irrelevant for this discussin, don't use in production code.
template<typename ContainerT>
void
print_container(const ContainerT& container, const std::string& name)
{
  std::cout << name << "nn";
  for (const auto& it : container)
    std::cout << *it << "n";
  std::cout << std::endl;
}
int
main()
{
  // Create a vector of 10 'Foo's.
  std::vector<Foo> items {};
  for (float x = 0.0f; x < 10.0f; x += 1.0f)
    items.emplace_back(x, std::sin(x));
  // For convenience, also create a vector of iterators into that vector.
  std::vector<FooIter> item_iterators {};
  for (auto it = items.begin(); it != items.end(); ++it)
    item_iterators.push_back(it);
  // Comparator based on the first element.
  const auto cmp1 = [](const FooIter it1, const FooIter it2)->bool{
    return it1->first < it2->first;
  };
  // Comparator based on the second element.
  const auto cmp2 = [](const FooIter it1, const FooIter it2)->bool{
    return it1->second < it2->second;
  };
  {
    // Create a set ordered by the value of the first element.
    std::set<FooIter, decltype(cmp1)> set1 {
      item_iterators.begin(), item_iterators.end(), cmp1
    };
    print_container(set1, "set1");
  }
  {
    // Create a set ordered by the value of the second element.
    std::set<FooIter, decltype(cmp2)> set2 {
      item_iterators.begin(), item_iterators.end(), cmp2
    };
    print_container(set2, "set2");
  }
  {
    // Create a vector of the three smallest (by the first element) values.
    std::vector<FooIter> vec1(3);
    std::partial_sort_copy(item_iterators.begin(), item_iterators.end(),
                           vec1.begin(), vec1.end(), cmp1);
    print_container(vec1, "vec1");
  }
  {
    // Create a vector of the three smallest (by the second element) values.
    std::vector<FooIter> vec2(3);
    std::partial_sort_copy(item_iterators.begin(), item_iterators.end(),
                           vec2.begin(), vec2.end(), cmp2);
    print_container(vec2, "vec2");
  }
  return 0;
}

输出为:

set1
[  0.0000,   0.0000]
[  1.0000,   0.8415]
[  2.0000,   0.9093]
[  3.0000,   0.1411]
[  4.0000,  -0.7568]
[  5.0000,  -0.9589]
[  6.0000,  -0.2794]
[  7.0000,   0.6570]
[  8.0000,   0.9894]
[  9.0000,   0.4121]
set2
[  5.0000,  -0.9589]
[  4.0000,  -0.7568]
[  6.0000,  -0.2794]
[  0.0000,   0.0000]
[  3.0000,   0.1411]
[  9.0000,   0.4121]
[  7.0000,   0.6570]
[  1.0000,   0.8415]
[  2.0000,   0.9093]
[  8.0000,   0.9894]
vec1
[  0.0000,   0.0000]
[  1.0000,   0.8415]
[  2.0000,   0.9093]
vec2
[  5.0000,  -0.9589]
[  4.0000,  -0.7568]
[  6.0000,  -0.2794]