如何在 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<
,无论如何,在此例程之外实现这是一件好事。
【讨论】:
【参考方案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;
但是,如果您像上面那样提供独立于运算符(不使用仿函数),那么还要提供 <=
、==
、>=
和 !=
以保持一致。
【讨论】:
+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 中放置看起来不可比较的对象?的主要内容,如果未能解决你的问题,请参考以下文章