首页 文章

C - 更好地替代STL 's map and Boost' s unordered_map? [关闭]

提问于
浏览
0

我总是发现C编程不方便的一件事是缺少一个好的map / dictionary / hash table / associative array容器类 . C#,Java和Objective-C都有这样的类,即Dictionary <>,Hashtable和NSDictionary,它们大多数都是开箱即用的,具有所有数据类型 . 但是STL的stl :: map和Boost的boost :: unordered_map都非常笨拙且过于冗长,即使对于平凡的日常任务也是如此 . 我想知道在一些开源库中是否存在C等价物,它更类似于上述平台的语法和功能 . 在这三个中,C#的Dictionary <>是我最喜欢的,因为它是强类型的,语法非常短,而且非常通用 . 所以这样的事情会很完美 . 我不确定这是否可行 . 如果没有,我想知道原因 . 以下是关于Boost和STL实现的主要痛点以及我想要的内容:

  • 首先,性能不是问题 . 内存分配,虚函数调用,O(n)复杂度 - 无所谓 . 无论如何,每天的词典只有几个条目 . 易用性至关重要 .

  • 语法通常应该类似于数组 . 这意味着一个多功能操作员[],如:

字典[key] = Value ; //插入和更新字典[key] = NULL; //删除元素if(dictionary [key])//检查元素是否存在 . 不应插入默认构造的值!

Java和Objective-C没有运算符重载,所以这是不可能的 . C#拥有它并且很好地利用它 . C不能这样做吗?

  • 值和键都可以是自定义用户定义类型或基本类型(整数,浮点数等) .

  • 存储用户定义的对象时,它们应该由shared_ptr引用 . 我正在使用Boost,所以这对于防止内存泄漏至关重要 . 其他三个平台要么是垃圾收集(C#/ Java),要么可以选择手动内存管理,引用计数和垃圾收集(目标C) . Boost在实现引用计数方面做得很好,所以它应该是可能的 . 这正是Objective-C的NSDictionary在ARC开启的基础上制作的 .

  • 存储用户定义的对象时,默认情况下应根据内存地址对它们进行比较 . 非常重要:用户定义的对象不需要哈希函数,operator ==,operator <,公共基类等 . 要求这些事情可以明确地将比较从内存地址更改为其他内容,例如字符串的按值比较 . 但大多数时候我们只想要内存地址比较 .

  • 存储原始数据类型时,应按值进行比较 . 它们是否在某个内部对象中包装/装箱应该与用户无关 . 再次,性能无关紧要 .

  • if(dictionary [key])应该可以检查给定键是否存在值 . 这不应该像在Boost和STL中那样插入默认的构造值对象 .

  • 应该为键和值强类型化 . 所以没有空虚* . 此外,键和值都不需要通用的基类,因为这会过于干扰并且会使第三方类更难以存储在 Map 中 .

  • 唯一键 . 不允许空值(空值导致删除) .

  • 键应该可以作为矢量或数组访问,并且可以通过索引遍历 . 迭代器需要太多输入 . 也就是说,我们应该能够写:

for(int i = 0; i <dictionary.getKeys() . getCount(); i){shared_ptr value = dictionary [dictionary.getKeys()[i]]; }

必须编写充满迭代器声明的庞大for循环,这会使源代码的清晰度变得混乱 . 迭代器的typedef也不好,因为它们只会因为你遇到的每一个新的“Go To Definition”而增加复杂性,特别是在阅读别人的代码时 .

我想我可以在列表中添加更多点数,但我会停在这里 . 您是否知道任何图书馆的 Map 类至少满足其中的大部分要点?我非常感谢任何建设性的反馈 .

1 回答

  • 10

    我将逐一阐述你的观点 . 在许多方面,你所要求的对于C来说是不现实的(就像在Java中询问支持自定义运算符的 Map 一样是不现实的) .

    以下是关于Boost和STL实施的主要观点以及我想要的内容:1 . 首先,性能不是问题所在 . 内存分配,虚函数调用,O(n)复杂度 - 无所谓 . 无论如何,每天的词典只有几个条目 . 易用性至关重要 .

    在问题出现之前,性能始终“不是问题”,当它出现问题时,它可以使您的项目陷入困境 . 这就是为什么通常,当性能不是问题时,仍然记住它是好的 . 没有自尊的图书馆会暴露具有API规范的概念实现(例如 Map /字典),例如“性能不是问题” . 如果有一个(虽然我认为不应该),它将以有效的方式实现,或者在库中你应该远离它 .

    2.语法通常应该类似于数组 . 这意味着一个多功能的运算符[],如:dictionary [key] = value; //插入和更新

    这已经实现了C std :: map

    dictionary [key] = NULL; //删除元素

    这对C来说是不现实的 . 它可以通过自定义引用包装器访问值并使用nullptr进行赋值来实现 . 问题是在C中,指针是一种不同的数据类型 .

    那就是这意味着什么?

    IdealMap<int, my_obj*> pointer_map;
    pointer_map[1] = new my_obj{};
    pointer_map[1] = nullptr;
    

    最后一行是将poiter_map [i]设置为NULL,还是确保从这一点对元素i的任何访问都会抛出“未找到”异常?

    您可以交替编写这样的实现:

    IdealMap<int, my_obj*> pointer_map;
    pointer_map[1] = new my_obj{};
    pointer_map[1] = IdealMap<int, my_obj*>::novalue;
    

    novalue 将是一个特殊常量,概念上代表"none" .

    if(dictionary [key])//检查元素是否存在 .

    再次,在通用映射中用C实现也不是一个好主意 . 考虑这张 Map :

    IdealMap<int, bool> bool_map;
    bool_map[0] = true;
    if(bool_map[0]) {...}
    

    你在这里检查一个元素是否存在于零位置,或者该元素是否为真?

    不应插入默认构造的值!

    通过将 Map 封装在自定义类中,这对于实现自己来说是微不足道的 .

    其余的点听起来更像购物清单 . 对不起,没有人写过匹配它的字典类(即 no, there is no library that wrote a dictionary exactly like you would like ) .

    必须编写充满迭代器声明的庞大for循环,这会使源代码的清晰度变得混乱 . 迭代器的typedef也不好,因为它们只会因为你遇到的每一个新的“Go To Definition”而增加复杂性,特别是在阅读别人的代码时 .

    写作庞大的for循环并不是你必须要做的事情 . 如果你有,你的问题是你的代码库(恕我直言)没有重构,而不是 Map .

相关问题