【问题标题】:Efficient way to store C++ object with multiple fields of different types in std::set/std::map在 std::set/std::map 中存储具有多个不同类型字段的 C++ 对象的有效方法
【发布时间】:2016-01-24 16:41:51
【问题描述】:

我有一个一般性问题。假设我有一个包含多个不同类型字段的 C++ 类。我想要/需要将此类的对象存储在 std::setstd::map 中(以便在 O(log(N)) 中访问它们。

为了做到这一点,我需要重载operator< 但是如果operator< 在我的情况下没有任何逻辑意义怎么办?例如,我有 class faceDescription,其中包含眼睛颜色、鼻子类型等字段。

最明显的是通过像这样比较每个字段来实现operator<

if (fieldA < other.fieldA)
{
    return true;
}
else if (fieldA == other.fieldA && fieldB < other.fieldB)
...

等等。但是如果我有很多字段,这个方法会太长,分支太多,难以阅读,可能难以维护。

我正在考虑将所有字段“打包”到缓冲区中,然后将其与 std::memcmp 之类的内容进行比较,但重点是某些字段可能是指针或不同的类/结构。

所以我的问题:

是否有一种有效且通用的方法来根据字段值为类(可能使用一些 std 方法)定义一个“唯一标识符”,以便这个“唯一标识符” " 可用于比较/排序该类的对象?

编辑

只是一个可以解释动机的例子,每个人都应该清楚:

假设使用人脸识别进行视频处理,以便程序接收人脸描述对象,并​​且必须计算每个人脸在给定视频中出现的次数。可能有成千上万张面孔。因此,有效的方法是将人脸描述对象的映射作为键和外观的数量作为值。

提前致谢!

【问题讨论】:

  • 如果它们没有自然顺序,你为什么要将它们存储在地图中?
  • 如果订购它们没有任何意义,为什么还要使用关联容器?
  • @JerryCoffin 以便在 O(log(N)) 中访问它们
  • 你打算用什么特征来找到它们?
  • @AlexLop。这并没有真正回答任何问题。是否有(例如)你不能将它们存储在向量中并在 O(1) 中访问它们的原因?

标签: c++ stl operator-overloading stdmap stdset


【解决方案1】:

您的问题实际上更像是三个问题合二为一:

我需要重载 operator&lt; 但如果 operator&lt; 在我的情况下没有任何逻辑意义怎么办?

您真的需要重载operator&lt;,只需提供一个自定义比较器到std::setstd::map(这是他们的第二个模板参数);默认为std::less(使用operator&lt;),但您可以提供任何定义元素之间严格的弱排序关系的二元仿函数。

最明显的方法是通过比较每个字段来实现operator&lt; [...] 但是如果我有很多字段,这个方法会太长,分支太多,难以阅读并且可能难以维护。 H3>

不幸的是,C++ 没有反射(甚至没有编译时反射,这可以解决这里的情况),所以没有简单的方法可以让“记住在我将所有字段添加到比较器时将它们添加到 @ 987654332@".

然而,异构值元组的字典比较已经由std::tuple 解决(在 C++11 中);您可以通过使用std::tie 并在返回的元组上调用&lt; 轻松实现operator&lt;(或FWIW,您的自定义比较器):

bool myComparer(const MyStruct &a, const MyStruct &b) {
    return std::tie(a.member1, a.member2, a.member3) < std::tie(b.member1, b.member2, b.member3);
}

您可以在 cppreference.com 上找到类似的示例 at its reference page

是否有一种有效且通用的方法来根据字段值为类定义一个“唯一标识符”(可能使用一些std 方法),以便这个“唯一标识符”可以用于比较/排序对象那个班?

创建一个唯一标识符来比较/排序对象(即满足严格弱排序的约束)取决于您的对象的确切细节 - 但可能,如果您说您的对象没有 有意义的排序(除了你可以通过按字典顺序比较它们的组件来强加的人为排序)你实际上并不想要这样的东西;您只是希望能够使用关联容器。

输入std::unordered_mapstd::unordered_set(实际上是标准诱饵名称后面的哈希表);他们需要的是一个“有点独特”的标识符,可以快速区分不同的键,AKA 一个哈希函数,他们可以平均在 O(1) 时间内检索您的元素。在 C++11 中,这个函数是std::hash

标准已经为原始类型和其他一些随机类型定义了重载;您可以通过组合struct 的各个组件的哈希值来定义自己的hash(在standard signature 之后;参见底部的示例专业化);组合可以从简单的 XOR 或 sum 到更详细的 like this

【讨论】:

  • 感谢您的详细解答。我没有提到它,但我有一个限制。 C++11 目前对我不可用,这就是为什么我提到 std::map/std::set 而不是他们的 C++11 '无序'变体。但是我不熟悉std::tie,它看起来对我有用,因为它比许多if(...) else... 条件更具可读性。但它也仅在 C++11 上可用。
【解决方案2】:

您可以创建自己的哈希函数,将类成员作为参数,然后,您可以使用这些哈希值作为键将对象存储在std::mapstd::unordered_map 结构中。这样您就不必费心将新对象与地图中的所有对象进行比较。您也可以将std::hash 用于此特定目的。

您可以为用户定义的类专门化 std::hash(来自reference):

#include <iostream>
#include <functional>
#include <string>

struct S
{
    std::string first_name;
    std::string last_name;
};

namespace std
{
    template<>
    struct hash<S>
    {
        typedef S argument_type;
        typedef std::size_t result_type;

        result_type operator()(argument_type const& s) const
        {
            result_type const h1 ( std::hash<std::string>()(s.first_name) );
            result_type const h2 ( std::hash<std::string>()(s.last_name) );
            return h1 ^ (h2 << 1);
        }
    };
}

int main()
{
    S s;
    s.first_name = "Bender";
    s.last_name =  "Rodriguez";
    std::hash<S> hash_fn;

    std::cout << "hash(s) = " << hash_fn(s) << "\n";
}

【讨论】:

  • 最好使用std::unordered_map,它可以解决哈希冲突。
  • 你知道这样的“哈希”对于两个不同的对象返回相同结果的概率是多少?另外,如果我有很多字段,为每种类型定义哈希函数,然后通过字段比较计算整个对象的一般哈希可能比本机字段更复杂。
  • @AlexLop.,哈希函数返回相当高的整数值,所以不会有太大问题。
【解决方案3】:

你考虑过使用元组吗?

// Multi-index map
map<tuple<int, char, float>, string> m;
m[make_tuple(31, 'd', 23.5f)] = "Just an idea";

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-08
    • 2019-04-01
    • 2014-04-15
    • 1970-01-01
    相关资源
    最近更新 更多