【问题标题】:Why do standard associative ordered containers allow `const char*` as their key?为什么标准关联有序容器允许 `const char*` 作为它们的键?
【发布时间】:2020-01-30 17:31:41
【问题描述】:

据我所知,我们永远不应该使用关系运算符来比较两个 const 字符串 <>... 因为它比较的是地址而不是值:

const char* sz1 = "Hello";
const char* sz2 = "hello";
if(sz1 < sz2);// not valid. So use strcmp instead.
  • 我注意到像map, multimap, set, multiset 这样的有序关联容器对其key 施加了限制,因此应该比较键以对容器中的元素进行排序。键的默认运算符是 &lt; 运算符。

一切都很清楚,直到我创建了 map, setconst char* 然后我得到的结果不正确:

std::set<const char*> scp{ "Hello", "World", "C++", "Programming" };    
std::set<std::string> sstr{ "Hello", "World", "C++", "Programming" };

// error
std::copy(scp.cbegin(), scp.cend(), std::ostream_iterator<const char*>(std::cout, " "));
std::cout << std::endl;

// Ok 
std::copy(sstr.cbegin(), sstr.cend(), std::ostream_iterator<std::string>(std::cout, " "));
std::cout << std::endl;
  • 很明显,scp 将指针与字符串进行比较,而 sstr 可以,只要 string 类定义了 &lt; 以正常工作。

  • 为什么 STL 允许这样做? (创建关键元素类型为char* 的关联容器)为什么这里甚至没有警告?

【问题讨论】:

  • 反论点:为什么要使用 可能 指向 C 样式字符串的指针来阻止标准容器,因为您不应该使用 C 样式C++ 中的字符串?
  • 仅仅因为它不适用于这种简单的情况并不意味着其他人不会有一个用例,其中指针作为键可能没有用。此外,您的情况可以通过提供您自己的使用strcmp的比较功能来解决。
  • 我什至认为sz1 &lt; sz2 是UB(而std::less&lt;&gt;{}(sz1, sz2) 不是)。
  • @Jarod42 除非它们指向同一个数组,是的。
  • 您可能只想比较指针。为什么禁止这样做(即使不寻常)?对于警告,表达比较指针意图的正确方法是什么?

标签: c++ stl stdmap stdset


【解决方案1】:

键的默认运算符是&lt; 运算符。

这不是真的。非散列关联容器的默认比较运算符是std::lessstd::less 使用 operator &lt; 进行比较,但有一个关键区别。不像operator &lt; where 中内置的指针

两个指针都不需要比较大于另一个。

source

std::less 有这个

任何指针类型的特化都会产生严格的总顺序,该顺序在这些特化之间是一致的,并且也与内置运算符&lt;&gt;&lt;=&gt;= 施加的偏序一致。

source

所以这是一个安全的操作,我们可以在地图中可靠地存储指针。

【讨论】:

  • 但在 Lipman 编辑的 C++ Primer 5 中:“关联容器对用作键的类型施加约束。我们将在第 11.4 节中介绍对无序容器中键的要求(p . 445). 对于有序容器——map、multimap、set 和 multiset——键类型必须定义一种比较元素的方法。默认情况下,库使用
  • @ItachiUchiwa 他过于简化/错了。默认情况下,它使用std::less,它使用operator &lt;,但在指针方面有更多保证。你可以看到in the C++ referencestd::set使用std::less作为Compare
  • 好的。非常感谢!
【解决方案2】:

正如其他人指出的那样,有时您可能想要指针比较,如果您不这样做,那么容器允许您提供自己的自定义比较运算符,如下所示:

#include <cstring>
#include <iostream>
#include <iterator>
#include <string>
#include <set>

struct CStrCmp {
    bool operator() (const char* lhs, const char* rhs) const {
        return strcmp(lhs, rhs) < 0;
    }
};
int main()
{
    std::set<const char*, CStrCmp> scp{ "Hello", "World", "C++", "Programming" };
    std::set<std::string> sstr{ "Hello", "World", "C++", "Programming" };

    // This works too now
    std::copy(scp.cbegin(), scp.cend(), std::ostream_iterator<const char*>(std::cout, " "));
    std::cout << std::endl;

    // Ok 
    std::copy(sstr.cbegin(), sstr.cend(), std::ostream_iterator<std::string>(std::cout, " "));
    std::cout << std::endl;
}

【讨论】:

    猜你喜欢
    • 2023-04-02
    • 1970-01-01
    • 1970-01-01
    • 2021-11-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-01
    • 2011-10-09
    • 1970-01-01
    相关资源
    最近更新 更多