60

我想比较使用不同分配器std::string分配的 STL 字符串,例如使用自定义 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++ 高级接口和运算符重载?

4

2 回答 2

37

用于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;
}

事实上,您可以为大多数标准容器定义这样的重载。您甚至可以在模板上对其进行模板化,但这太极端了。

于 2012-10-09T18:07:12.787 回答
20

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

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==()对于仅在分配器类型上有所不同的容器类型,支持运算符(这可能扩展到除 之外的其他运算符)可能是合理的。

于 2012-10-09T18:33:19.640 回答