在一个集合中保存一堆constchar*的最简单、最安全的方法

Simplest, safest way of holding a bunch of const char* in a set?

本文关键字:最简单 安全 方法 constchar 一个 集合 保存 一堆      更新时间:2023-10-16

我想在std::set容器[1]中保存一堆const char指针。std::set模板需要一个comparator函数,标准C++库提供std::less,但它的实现是基于直接比较两个键,这不是指针的标准。

我知道我可以定义自己的函子,并通过将指针强制转换为整数并进行比较来实现运算符(),但有更干净、"标准"的方法吗?

请不要建议创建std::字符串,这是浪费时间和空间。这些字符串是静态的,因此可以根据它们的地址来比较它们是否相等。

1:指针指向静态字符串,所以它们的寿命没有问题——它们不会消失。

如果您不想将它们包装在std::strings中,您可以定义一个函子类:

struct ConstCharStarComparator
{
  bool operator()(const char *s1, const char *s2) const
  {
    return strcmp(s1, s2) < 0;
  }
};
typedef std::set<const char *, ConstCharStarComparator> stringset_t;
stringset_t myStringSet;

只需继续使用较少的默认排序。该标准保证,即使对于指向不同对象的指针,也能使用更少的指针:

"对于模板greater、less、greater_equal和less_equal指针类型产生总顺序,即使内置运算符<,><=,>=不要。"

对于像你的set<const char*>这样的东西,保证是存在的。

"优化方式"

如果我们忽略了"过早优化是万恶之源",标准的方法是添加一个比较器,这很容易写:

struct MyCharComparator
{
   bool operator()(const char * A, const char * B) const
   {
      return (strcmp(A, B) < 0) ;
   }
} ;

与一起使用

std::set<const char *, MyCharComparator>

标准方式

使用a:

std::set<std::string>

即使你在里面放了一个静态的constchar*,它也会起作用(因为std::string与constchar*不同,它的内容是可比较的)。

当然,如果你需要提取数据,你必须通过std::string.c_str()来提取数据。另一方面,由于它是一个集合,我想你只想知道"AAA"是否在集合中,而不是提取"AAA"的值"AAA"。

注意:我确实读过"请不要建议创建std::string",但后来,你问了"标准"的方式

"永远不要做"的方式

我在回答后注意到以下评论:

请不要建议创建std::字符串,这是浪费时间和空间字符串是静态的,因此可以根据它们的地址来比较它们的(in)相等性

这有点C的味道(使用了不推荐使用的"static"关键字,可能是用于std::string bashing的过早优化,以及通过它们的地址进行字符串比较)

无论如何,您不想通过字符串的地址来比较它们因为我想你最不想要的就是有一个包含的集合

{ "AAA", "AAA", "AAA" }

当然,如果只使用相同的全局变量来包含字符串,那就另当别论了。

在这种情况下,我建议:

std::set<const char *>

当然,如果您比较内容相同但变量/地址不同的字符串,它将不起作用。

当然,如果static const char*字符串是在标头中定义的,那么它将无法与这些字符串一起使用。

但这是另一回事。

根据"束"的大小,我倾向于在集合中存储相应的std::string束。这样,您就不必编写任何额外的粘合代码。

集合必须包含const char*吗?

脑海中立刻浮现的是将字符串存储在std::string中,并将其放入std::set中。这将允许比较而没有问题,并且您总是可以通过简单的函数调用获得原始const char*

const char* data = theString.c_str();

要么使用比较器,要么使用要包含在集合中的包装器类型。(注意:std::string也是包装器……)

const char* a("a");
const char* b("b");
struct CWrap {
    const char* p;
    bool operator<(const CWrap& other) const{
        return strcmp( p, other.p ) < 0;
    }
    CWrap( const char* p ): p(p){}
};
std::set<CWrap> myset;
myset.insert(a);
myset.insert(b);

其他人已经发布了大量的解决方案,展示了如何与const char*进行词汇比较,所以我不会麻烦了。

请不要建议创建std::字符串,这是浪费时间和空间。

如果std::string是浪费时间和空间,那么std::set也可能是浪费时间或空间。CCD_ 12中的每个元素都与空闲存储区分开分配。根据程序使用集合的方式,这对性能的影响可能大于std::set的O(logn)查找对性能的帮助。使用另一种数据结构(如排序的std::vector或在编译时排序的静态分配数组)可能会得到更好的结果,具体取决于集合的预期生存期。

标准C++库提供std::less,但它的实现是基于直接比较两个键,这不是指针的标准。

这些字符串是静态的,因此可以根据它们的地址来比较它们是否相等。

这取决于指针指向什么。如果所有的键都是从同一个数组中分配的,那么使用operator<来比较指针并不是未定义的行为。

包含单独静态字符串的数组示例:

static const char keys[] = "applebananacantaloupe";

如果创建一个std::set<const char*>,并用指向该数组的指针填充它,则它们的顺序将得到定义。

然而,如果字符串都是单独的字符串文字,那么比较它们的地址很可能会涉及未定义的行为。它是否有效取决于您的编译器/链接器实现、如何使用它以及您的期望。

如果您的编译器/链接器支持字符串池并启用了它,那么重复的字符串文字应该具有相同的地址,但它们在任何情况下都能保证吗?依靠链接器优化获得正确的功能是否安全?

如果只在一个翻译单元中使用字符串文字,则集合排序可能会基于字符串首次使用的顺序,但如果将另一个翻译单位更改为使用相同的字符串文字之一,则集合顺序可能会更改。

我知道我可以定义自己的函子,并通过将指针强制转换为整数并将它们进行比较来实现运算符()

将指针投射到uintptr_t似乎比使用指针比较没有任何好处。无论哪种方式,结果都是相同的:具体实现。

可能由于性能原因,您不想使用std::string。

我正在运行MSVC和gcc,他们似乎都不介意这个:

bool foo = "blah" < "grar";

编辑:但是,这种情况下的行为没有具体说明。查看评论。。。

他们也不抱怨std::set<const char*>

如果你使用的编译器确实有问题,我可能会继续使用你建议的函数,它将指针投射到int s。

编辑:嘿,我被投票否决了。。。尽管他是这里为数不多的最直接回答他的问题的人之一。我是Stack Overflow的新手,如果发生这种情况,有什么办法保护自己吗?话虽如此,我会尽量在这里:

问题不在于寻找std::string解决方案。每次在集合中输入std::string时,它都需要复制整个字符串(直到C++0x成为标准)。此外,每次进行集合查找时,都需要进行多个字符串比较。

然而,将指针存储在集合中不会导致字符串复制(您只是四处复制指针),并且每次比较都是对地址的简单整数比较,而不是字符串比较。

问题是,存储指向字符串的指针是可以的,我看不出我们有什么理由立即认为这句话是错误的。如果您知道自己在做什么,那么与std::string或调用strcmp的自定义比较相比,使用const char*可以获得相当大的性能提升。是的,它不太安全,更容易出错,但这些都是性能的常见权衡,由于问题从未说明应用程序,我认为我们应该假设他已经考虑了利弊,并决定支持性能。