比较使用不同分配器的STL字符串

Comparing STL strings that use different allocators

本文关键字:STL 字符串 分配器 比较      更新时间:2023-10-16

我想比较用不同分配器分配的STL字符串,例如普通std::string与使用自定义STL分配器的字符串。不幸的是,在这种情况下,通常的operator==()似乎不起作用:

// Custom STL allocator to allocate char's for string class
typedef MyAllocator<char> MyCharAllocator;
// Define an instance of this allocator
MyCharAllocator myAlloc;
// An STL string with custom allocator
typedef std::basic_string
<
char, 
std::char_traits<char>, 
MyCharAllocator
> 
CustomAllocString;
std::string s1("Hello");
CustomAllocString s2("Hello", myAlloc);
if (s1 == s2)  // <--- ERROR: doesn't compile
...

特别是,MSVC10(VS2010 SP1)会发出以下错误消息:

错误C2678:二进制"==":找不到使用左手的运算符类型为"std::string"的操作数(或者没有可接受的转换)

因此,较低级别的(可读性较差)代码如下:

if (strcmp(s1.c_str(), s2.c_str()) == 0)
...

应该使用。

(例如,在存在不同分配字符串的std::vector的情况下,这也特别令人讨厌,因为通常的简单v[i] == w[j]语法无法使用。)

这对我来说似乎不是很好,因为自定义分配器会更改请求字符串内存的方式,但字符串类的接口(包括与operator==()的比较)与字符串分配内存的特定方式无关。

我这里缺了什么吗?在这种情况下,是否可以保留C++高级接口和运算符重载?

使用std::lexicographical_compare进行小于比较:

bool const lt = std::lexicographical_compare(s1.begin(), s1.end(),
s2.begin(), s2.end());

对于相等比较,可以使用std::equal:

bool const e = s1.length() == s2.length() &&
std::equal(s1.begin(), s1.end(), s2.begin());

或者,您可以回到strcmp(或者实际上是memcmp,因为它具有正确的语义;请记住,C++字符串比C字符串更通用),正如您所建议的那样,它可以潜在地­巧妙地使用一些较低级别的魔术,比如一次比较整个机器词(尽管上述算法也可能因此而专门化)。我会说,测量和比较。对于短字符串,标准库算法至少可以很好地自我描述。


基于@Dietmar下面的想法,您可以将这些函数封装到模板重载中:

#include <string>
#include <algorithm>
template <typename TChar,
typename TTraits1, typename TAlloc1,
typename TTraits2, typename TAlloc2>
bool operator==(std::basic_string<TChar, TTraits1, TAlloc1> const & s1,
std::basic_string<TChar, TTraits2, TAlloc2> const & s2)
{
return s1.length() == s2.length() &&
std::equal(s1.begin(), s1.end(), s2.begin());
}

用法示例:

#include <ext/malloc_allocator.h>
int main()
{
std::string a("hello");
std::basic_string<char, std::char_traits<char>, __gnu_cxx::malloc_allocator<char>> b("hello");
return a == b;
}

事实上,您可以为大多数标准容器定义这样的重载。你甚至可以把它模板化在一个模板上,但那将是极端的。

标准仅使用同质字符串类型定义运算符,即所有模板参数都需要匹配。但是,您可以在定义分配器的命名空间中定义一个合适的相等运算符:依赖于参数的查找会在那里找到它。如果你选择实现你自己的分配操作符,它看起来像这样:

bool operator== (std::string const& s0,
std::basic_string<char, std::char_traits<char>, MyCharAllocator> const& s1) {
return s0.size() == s1.size() && std::equal(s0.begin(), s0.end(), s1.begin()).first;
}

(加上其他一些过载)。将其提升到下一个层次,甚至可以根据容器需求定义各种关系运算符的版本,而不限制模板参数:

namespace my_alloc {
template <typename T> class allocator { ... };
template <typename T0, typename T1>
bool operator== (T0 const& c0, T1 const& c1) {
return c0.size() == c1.size() && std::equal(c0.begin(), c0.end(), c1.end);
}
...
}

显然,操作符可以被限制为特定的容器类型,不同之处仅在于它们的分配器模板参数。

关于为什么标准没有定义混合类型比较,不支持混合类型比较的主要原因可能是您实际上一开始就不想在程序中混合分配器!也就是说,如果你需要使用分配器,你会使用一个分配器类型,它封装了一个动态多态的分配策略,并且总是使用产生的分配器类型。这样做的原因是,否则你会得到不兼容的接口,或者你需要把所有东西都做成一个模板,也就是说,你想保留一些正在使用的词汇类型。当然,即使只使用一种额外的分配器类型,也会有两种词汇表字符串类型:默认实例化和特殊分配的实例化。

也就是说,不支持混合类型比较还有另一个潜在的原因:如果operator==()真的变成了两个值之间的比较,就像分配器不同的情况一样,它可能会产生一个更广泛的值相等定义:std::vector<T>() == std::deque<T>应该得到支持吗?如果不是,为什么具有不同分配器的字符串之间的比较会很特别?当然,分配器是std::basic_string<C, T, A>的一个不显著的属性,这可能是忽略它的一个很好的理由。我不确定是否应该支持混合类型比较。对于仅在分配器类型上不同的容器类型,支持运算符(这可能扩展到operator==()以外的其他运算符)可能是合理的。