如何在 C++ std::set 中放置看起来不可比较的对象?

Posted

技术标签:

【中文标题】如何在 C++ std::set 中放置看起来不可比较的对象?【英文标题】:How to place objects that seem not be comparable in a C++ std::set? 【发布时间】:2009-08-26 21:53:40 【问题描述】:

假设我想将标识服务器的对象放入 stl set。然后我必须确保我也为这些对象实现了operator<,否则我会遇到编译器错误:

struct ServerID

  std::string name; // name of the server
  int port;
;

std::set<ServerID> servers; // compiler error, no operator< defined

这只是我想让对象具有可比性的常见问题的一个示例。

我目前的解决方案通常是这样的:

bool operator< (const ServerID & lhs, const ServerID & rhs)

  if (lhs.name != rhs.name)
  
    return lhs.name < rhs.name;
  
  else
  
    return lhs.port < rhs.port;
  

这只是我自己找到的一个解决方案。但我怀疑这个问题也可能在计算机科学中得到认可。所以如果我很幸运,有一个更好的解决方案。任何人都可以暗示我吗?

【问题讨论】:

为什么不用一个 int 来识别带有 ID 号的服务器? Re:一般来说,你可能会想到“元组”,匿名结构,其中项目是 的 typedef 以获得相同的效果。但正如其他人所说,不用担心,因为您的完全独立的类型是规范的解决方案。 【参考方案1】:

我建议不要将其实现为 operator

struct server

   std::string name;
   int port;
;
struct name_then_port : public std::binary_function<server,server,bool>

   bool operator()( server const & lhs, server const & rhs ) 
      // using litb approach (more efficient as it does not call both < and == on strings:
      int cmp = lhs.name.compare(rhs.name);
      return ( cmp < 0 ) || ((cmp==0) && ( lhs.port < rhs.port));
   
;
struct port_then_name : public std::binary_function<server,server,bool>

   bool operator()( server const & lhs, server const & rhs ) 
      return (lhs.port < rhs.port) || ((lhs.port==rhs.port) && (lhs.name<rhs.name));
   
;
int main()

   std::set< server, name_then_port > servers; // or:
   std::set< server, port_then_name > servers2;

关于这个问题之前是否已经发现的问题,它已经发现了。通用解决方案正是您发布的内容:lexicographical order。虽然该术语通常指的是字符串排序,但排序是相同的:取第一个元素,如果没有定义顺序进行比较,取下一个数据元素并迭代。

【讨论】:

我没有想到这个选项。这不完全是我问题的答案,但无论如何谢谢:) 这是对标题中问题的回答。我已经更新了答案,以涵盖您问题的最后一部分。 给你一个 +1 也让你成为第一个使用仿函数方法 xD【参考方案2】:

您的解决方案是规范的。我不知道你会如何以一种更好的方式来做这件事。

要对此进行扩展,如果您的班级中有n 成员,您会发现您必须比较这些字段的一些数量才能建立严格的排序。没有真正的方法可以解决这个问题,尽管您可能会发现,如果您对比较进行排序,以便更有可能有助于比较将首先出现。这有助于它更快地退出比较。

在某些情况下可能会有所帮助(如果您发现性能受比较支配)是建立“排序键” - 比较字符串可能会很昂贵。排序键是一个整数,可用于对对象进行快速比较。如果排序键比较小于,那么字符串也可以。

在您的情况下,简单的排序键可能涉及将字符串的二进制表示视为整数 - 顺便说一下,这有很多错误 - 然后比较整数而不是字符串。

在 Windows 中,LCMapString 函数可用于以这种方式为字符串生成排序键。我认为您可以使用像 memcmp 这样的快速函数来比较字符串,而不是使用较慢的字符串比较。如果您要进行不区分大小写的比较或使用全范围的 unicode 字符并希望根据其规则进行正确比较,这将更加有用。

【讨论】:

如果一个类有 n 个成员,那么我的 operator 我在排序键的概念上添加了一些细节 - 在您的情况下不太可能有太多帮助【参考方案3】:

我通常写成:

return x.name < y.name ||
       x.name == y.name && x.port < y.port;

...您可以继续扩展您拥有的尽可能多的成员变量。此方案尽快短路,消除分支。

请注意,这需要为每个成员变量定义operator&lt;,无论如何,在此例程之外实现这是一件好事。

【讨论】:

【参考方案4】:

我会使用string::compare

bool operator< (const ServerID & lhs, const ServerID & rhs) 
  int lcr = lhs.name.compare(rhs.name);
  return lcr < 0 || (lcr == 0 && lhs.port < rhs.port);

如果将其进行比较对您来说没有意义,并且唯一的用途是将其填充到 set 中,您可以使用仿函数

struct ServerIdCompare 
  bool operator()(const ServerID & lhs, const ServerID & rhs) const 
    int lcr = lhs.name.compare(rhs.name);
    return lcr < 0 || (lcr == 0 && lhs.port < rhs.port);
  
;

std::set<ServerID, ServerIdCompare> servers;

但是,如果您像上面那样提供独立于运算符(不使用仿函数),那么还要提供 &lt;===&gt;=!= 以保持一致。

【讨论】:

+1 使用 string::compare 以避免进行两次(可能是昂贵的)比较。 +1 通过提供所有比较运算符(如果其中一个已定义)来保持一致性(好吧,更多或虚拟 +1,我不能投票两次)【参考方案5】:

如果您所需要的只是井井有条,并且您不在乎该顺序是什么,那么您的解决方案就可以了。

【讨论】:

【参考方案6】:

归根结底,您只需想出一个 比较功能,满足您的即时需求。这可以是 困难 - 例如,您将如何比较两个不同大小的位图?

【讨论】:

我心中的黑客说:if (bmp1.width != bmp2.width) return bmp1.width 不幸的是,除非您希望这些位图比较相等(在这种情况下,只能将其中一个放在 std::set 中),否则您必须提供一个可以比较每个像素的函数。 【参考方案7】:

在这种情况下,如果顺序无关紧要,由于字符串比较的成本,您可能希望在字符串之前比较端口。

【讨论】:

【参考方案8】:

我倾向于将 operator

【讨论】:

【参考方案9】:

您的解决方案几乎是正确的方法。您的比较函数应设置为唯一标识每个服务器(这实际上意味着什么取决于您的用例),因此比较名称/端口可能就足够了。

如果您知道您不会有两个具有相同名称但端口不同的服务器(或者您希望将它们视为相同),那么您可以删除比较函数的这一部分。一个更现实的例子是,如果您的服务器对象中有更多与服务器本身的身份无关的成员(例如“最后请求”缓存);在这种情况下,您可能不希望您的集合基于此字段进行区分,因此您不会将其包含在比较函数中。 OTOH,无论如何,这可能不是服务器对象的最佳设计。

如果您发现难以回答“何时应将两台服务器(对象)视为相同?”的问题?那么你可能根本不需要一套。

【讨论】:

以上是关于如何在 C++ std::set 中放置看起来不可比较的对象?的主要内容,如果未能解决你的问题,请参考以下文章

C++ 为啥在向量段错误中放置对象?

如何使用自动布局约束在单个屏幕中放置两个视图

在 C++ 中的可变长度函数中放置默认值参数的位置?

无论如何在qt的同一个窗口中放置一个QTreeView和一个QChartView?

在 C++ 对象中放置 int 矩阵数组时遇到问题

自动布局不适用于故事板中放置的项目