【问题标题】:C++ unordered_map with char* as key以 char* 为键的 C++ unordered_map
【发布时间】:2013-12-18 04:51:08
【问题描述】:

在尝试使用容器unordered_mapchar* 作为键时,我感到筋疲力尽(在Windows 上,我使用的是VS 2010)。我知道我必须为char* 定义我自己的比较函数,它继承自binary_function。下面是一个示例程序。

#include<unordered_map>
#include <iostream>
#include <string>
using namespace std;

template <class _Tp>  
struct my_equal_to : public binary_function<_Tp, _Tp, bool>  
{  
    bool operator()(const _Tp& __x, const _Tp& __y) const  
    { return strcmp( __x, __y ) == 0; }  
};

typedef unordered_map<char*, unsigned int, ::std::tr1::hash<char*>,  my_equal_to<char*> > my_unordered_map;
//typedef unordered_map<string, unsigned int > my_unordered_map;

my_unordered_map location_map;

int main(){
    char a[10] = "ab";
    location_map.insert(my_unordered_map::value_type(a, 10));
    char b[10] = "abc";
    location_map.insert(my_unordered_map::value_type(b, 20));

    char c[10] = "abc";
    location_map.insert(my_unordered_map::value_type(c, 20));

    printf("map size: %d\n", location_map.size());
    my_unordered_map::iterator it;
    if ((it = location_map.find("abc")) != location_map.end())
    {
        printf("found!\n");
    }

    return 0;
} 

我插入相同的 C 字符串 abc 两次并查找它。第二次插入应该会失败,并且 unordered_map 中将只有一个 abc。但是输出的大小是3,这里的比较功能好像不能正常工作。

另外,我得到了另一个关于find函数的奇怪结果,通过多次运行程序,发现结果甚至发生了变化!有时会找到字符串abc,而有时找不到abc

有人可以帮我解决这个问题吗?非常感谢您的帮助!

++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++

编辑:我自己为char* 定义了一个哈希函数后,程序可以正常工作。下面列出了完整的程序代码。谢谢大家。

#include<unordered_map>
#include <iostream>
using namespace std;

template <class _Tp>  
struct my_equal_to : public binary_function<_Tp, _Tp, bool>  
{  
    bool operator()(const _Tp& __x, const _Tp& __y) const  
    { return strcmp( __x, __y ) == 0; }  
};


struct Hash_Func{
    //BKDR hash algorithm
    int operator()(char * str)const
    {
        int seed = 131;//31  131 1313 13131131313 etc//
        int hash = 0;
        while(*str)
        {
            hash = (hash * seed) + (*str);
            str ++;
        }

        return hash & (0x7FFFFFFF);
    }
};

typedef unordered_map<char*, unsigned int, Hash_Func,  my_equal_to<char*> > my_unordered_map;


int main(){
    my_unordered_map location_map;

    char a[10] = "ab";
    location_map.insert(my_unordered_map::value_type(a, 10));
    char b[10] = "abc";
    location_map.insert(my_unordered_map::value_type(b, 20));

    char c[10] = "abc";
    location_map.insert(my_unordered_map::value_type(c, 20));

    printf("map size: %d\n", location_map.size());
    my_unordered_map::iterator it;
    if ((it = location_map.find("abc")) != location_map.end())
    {
        printf("found!\n");
    }

    return 0;
}

注意:使用 char* 作为 unordered_map 或其他 STL 容器的键类型可能很危险,安全的方法(似乎是唯一的方法)是:在 main 函数中,newmalloc堆上的一个块(例如一个c字符串数组)并用c字符串填充它。将这些 c 字符串插入 unordered_map。分配的内存块在 main 函数结束时被释放(deletefree)。

【问题讨论】:

  • 你不需要从binary_function继承。它甚至可能被弃用;我现在查不出来。
  • 这不是问题,但是包含两个连续下划线的名称(__x__y)和以下划线后跟大写字母(_Tp)的名称被保留到实现(编译器及其库)。不要使用它们。
  • 你的意思是指向char*的键字符串可以改变吗?因为我认为您希望 key 是 const char * 指向的常量字符串。让char *const char * 的每一次出现都支持这一点
  • @rahul.deshmukhpatil 还有更深层次的问题,请看一下答案和下面的cmets。
  • 只是一个注释,你会想#include &lt;functional&gt;binary_function

标签: c++ map


【解决方案1】:

你的比较器很好(尽管传递一个 nullptr 是未定义的,可能应该被处理)

哈希 ::std::tr1::hash&lt;char*&gt; 正在对指针进行哈希处理,因此每个“abc”(通常)进入不同的存储桶

您需要编写自己的哈希函数来保证 hash("abc") 总是给出相同的答案

现在 - 性能会很糟糕,但是有一个返回 0 的哈希 - 你应该看到第二个“abc”匹配第一个

根据 cmets - 使用 std::string 可简化内存管理并提供库支持的哈希和比较器,因此只需 std::unordered_map&lt;std::string, X&gt; 即可。这也意味着在删除 unordered map 后,所有字符串都会为您解除分配。您甚至可以安全地从堆栈上的 char 数组中实例化 std::strings

如果您仍想使用char *,那么您仍然需要自己的比较器和哈希,但您可以使用std::shared_ptr 为您管理内存(不要使用堆栈实例 - 使用new char[]) 然后您将拥有std::unordered_map&lt;shared_ptr&lt;char *&gt;, X&gt;,但以后不会因内存泄漏而出现并发症。

如果您仍想使用char *,那么您就在正确的轨道上,但重要的是您使用内存泄漏工具(如 purify 或 valgrind)来确保您真正控制所有内存管理。 (这通常对任何项目都是一个好主意)

最后,应该避免使用全局变量。

【讨论】:

  • 使用指针作为 STL 映射键是一个相当冒险的选择,应该劝阻而不是修补。请注意,他使用了一个全局映射,其中填充了指向自动字符数组的指针。这种做法几乎肯定会在他的脸上爆炸。
  • @Jeff OP 的问题是如何在std::unordered_map 中使用char * 而不是如何通过使用字符串来避免使用char *,如前所述“以下是一个示例程序” - 我的答案解决了问题 - 您的评论是一个有效的问题 - 但不是解决方案。
  • @GlennTeitelbaum 这里的全局unordered_map 变量会有风险吗?
  • @Glenn 我强烈反对。很明显,这个问题本身就意味着对对象生命周期和所有权的误解,尤其是在 STL 容器方面。我要求您考虑以下问题:在实际使用中,如何分配 c 字符串,以及如何在没有与指针一起存储长度的情况下释放它们?如果您认为他在堆栈上声明了固定数量的字面初始化值(或进行显式堆栈分配),他为什么需要制作一个映射并将其传递给子程序?
  • "以及如何在没有与指针一起存储的长度的情况下释放它们"您不需要长度来释放它们,并且示例很好,因为它们实际上是在堆栈上分配的,但保留在使用过程中的范围内。一切都不必是 std::string 才能工作。
【解决方案2】:

像上面那样使用 char 指针作为键几乎肯定不是你想要做的。

STL 容器处理存储的值,在 std::unordered_map&lt;char *, unsigned int, ...&gt; 的情况下,您正在处理指向 c 字符串的指针,在随后的插入/删除检查中甚至可能不存在。

请注意,您的my_unordered_map 是一个全局变量,但您正在尝试插入本地字符数组 a、b 和 c。当插入的 c 字符串超出范围时,您希望您的比较函数 my_equal_to()strcmp() 是什么? (你突然有了指向随机垃圾的键,可以与新插入的未来值进行比较。)

重要的是,STL 映射键是可复制的值,其含义不能被外部程序行为改变。 您几乎可以肯定地使用std::string 或类似名称作为您的关键值,即使它们的构造乍一看似乎很浪费。

以下内容将完全按照您的预期工作,并且更加安全:

#include <unordered_map>
#include <iostream>
#include <string>

using namespace std;

// STL containers use copy semantics, so don't use pointers for keys!!
typedef unordered_map<std::string, unsigned int> my_unordered_map;

my_unordered_map location_map;

int main() {
    char a[10] = "ab";
    location_map.insert(my_unordered_map::value_type(a, 10));

    char b[10] = "abc";
    location_map.insert(my_unordered_map::value_type(b, 20));

    char c[10] = "abc";
    location_map.insert(my_unordered_map::value_type(c, 20));

    cout << "map size: " << location_map.size() << endl;

    my_unordered_map::iterator it;
    if ((it = location_map.find("abc")) != location_map.end()) {
        cout << "found \"" << it->first << "\": " << it->second << endl;
    }

    return 0;
}

【讨论】:

  • 谢谢,但我之所以要使用char* 而不是std::string 是因为程序的性能。每次插入字符串时都会创建一个匿名对象std::string。而且我不明白为什么全局unordered_map 变量会成为问题。实际上传递给my_equal_to()的指针都是这个程序中的虚拟地址,我不明白插入的c字符串是如何超出范围的。你能详细说明一下吗?
  • @Bloodmoon 您在修复中的内容有效,但在实际使用场景中将完全失败。 char a[10]、b[10] 和 c[10] 数组在堆栈上创建,一旦超出范围就会有效地消失。为了说明这一点,创建一个 void foo() 和一个 int bar(),其中 foo() 执行 a/b/c 定义和映射插入。在 bar() 中,创建一个 int x[100] 并用 1 到 100 的值填充它,然后将它们相加并返回值。在 main() 中,调用 foo(),然后调用 bar(),然后再查找值。它会失败,因为您覆盖了用于包含 char 数组的相同内存值...
  • @Bloodmoon 总结一下我上面较长的评论,您的问题不是保存字符数组的内存将被取消映射,而是保存字符的堆栈地址空间最终将被覆盖。您的示例有效的唯一原因是您在与每个地图插入相同的函数中定义了 a/b/c。事实上,你可能会忽略我对 bar() 函数的看法,第一个“map size:”打印调用可能会破坏 foo() 调用中设置的 char 数组...
  • 我现在明白你的意思了。如果我在严格的约束下使用带有 char* 的 unordered_map 会怎样:在main 函数中,new 堆上的一个块(例如一个 c 字符串数组)并用 c 字符串填充它。将这些 c 字符串插入 unordered_map。这些插入可能会被某些查找混淆。分配的内存块在main 函数结束时被释放(即delete)。这种用法会安全吗?
  • 这就是为什么 C++ 很快就会像 Java 一样慢的一种精神。你会获得更多的内存、更多的间接性等。这对我来说真的很糟糕。
【解决方案3】:

当您定义诸如 "abc" 之类的内容时,它会被分配一个 const char*。每次您在程序中写入 "abc" 时,都会分配一个新的内存。所以:

const char* x = "abc";
const char* y = "abc";
return x==y;

将始终返回 false,因为每次写入 "abc" 时都会分配新内存(对不起,如果我听起来有点重复)。

【讨论】:

  • 我没有直接比较指针,正如my_equal_to 所说。好像不行?
  • 他们可能会或可能不会获得唯一的地址。它取决于实施和情况。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-11-09
  • 2018-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-08
  • 1970-01-01
相关资源
最近更新 更多