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

Mr.*_*C64 59 c++ string memory-management stl

我想比较使用不同分配器分配的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
   ...
Run Code Online (Sandbox Code Playgroud)

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

错误C2678:二进制'==':找不到哪个运算符带有'std :: string'类型的左操作数(或者没有可接受的转换)

所以,这样的低级(不太可读)代码:

if (strcmp(s1.c_str(), s2.c_str()) == 0)
   ...
Run Code Online (Sandbox Code Playgroud)

应该使用.

(在例如存在std::vector不同分配的字符串的情况下,这也是特别烦人的,其中通常的简单v[i] == w[j]语法不能被使用.)

这对我来说似乎不太好,因为自定义分配器改变了请求字符串内存的方式,但字符串类的接口(包括与之比较operator==())独立于字符串分配其内存的特定方式.

这里有什么我想念的吗?在这种情况下,是否可以保持C++高级接口和运算符重载?

Ker*_* SB 37

使用std::lexicographical_compare了小于比较:

bool const lt = std::lexicographical_compare(s1.begin(), s1.end(),
                                             s2.begin(), s2.end());
Run Code Online (Sandbox Code Playgroud)

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

bool const e = s1.length() == s2.length() &&
               std::equal(s1.begin(), s1.end(), s2.begin());
Run Code Online (Sandbox Code Playgroud)

或者,您可以重新开始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());
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

#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;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 另一种方法是`s1.compare(s2.c_str())` (4认同)
  • @MarkRansom:不,为什么?我所关心的是char类型是相同的,所以我可以比较它们. (3认同)
  • 似乎是一个合理的解决方法,但问题似乎是"在这种情况下是否可以保持C++高级接口和运算符重载?",而不是"如何使用其他STL函数比较2个这样的字符串" (2认同)
  • @KerrekSB:我认为问题在于特征是如何比较字符串的.我不确定这里有多相关. (2认同)
  • @Kerrek:但是可以获得可读性.你可以抛弃`enable_if`,因为它是从'charT`暗示用于两个字符串. (2认同)

Die*_*ühl 19

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

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;
}
Run Code Online (Sandbox Code Playgroud)

(加上一些其他重载).将此提升到新的水平,根据容器需求定义各种关系运算符的版本甚至不限制模板参数甚至是合理的:

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);
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

显然,运算符可以限制为特定的容器类型,仅在其分配器模板参数方面有所不同.

关于标准没有定义混合类型比较的原因,不支持混合类型比较的主要原因可能是你实际上不想在程序中首先混合分配器!也就是说,如果您需要使用分配器,则可以使用分配器类型,该类型封装动态多态分配策略并始终使用生成的分配器类型.这样做的原因是,否则您将获得不兼容的界面,或者您需要将所有内容都设为模板,即您希望保留某些级别的词汇表类型.当然,即使只使用一个额外的分配器类型,您也有两种词汇表类型:默认实例化和特殊分配的实例化.

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

  • "*如果没有,为什么不同分配器的字符串之间的比较是特殊的?*"因为它们都是*字符串*,它们存储相同类型的字符.它们可能来自不同的记忆,但该记忆的*排列*是相同的.它们以完全相同的方式在该存储器上运行,并且它的含义是相同的.根据所有权利,分配器甚至不应成为容器签名的一部分; 类型的用户既不知道也不关心该类型的内存来自何处,除非他们*具有*to. (5认同)
  • "*可以完全不同*"这就是我的观点:*它不应该是*.就像`std :: shared_ptr`的布局和实现不会随分配器而改变.就像`std :: function`的布局和实现不会随分配器而改变.这是C++标准委员会的一个错误,他们拒绝(或无法)修复. (2认同)
  • @Nicol:你错过了`shared_ptr`和`function`都需要求助于*type erasure*(是的,我知道他们已经需要它了,但是,容器没有),这有点需要动态内存分配大于两个指针的对象(有状态分配器,任何人?).你会如何分配内存?使用分配器给容器?不,这不应该用于它.还有*另一个*分配器已经通过(有点像`shared_ptr`的构造函数可以采用)? (2认同)