STL角色特征有​​什么意义?

时间:2011-03-16 00:44:14

标签: c++ string stl stdstring char-traits

我注意到在我的SGI STL参考文件副本中,有一个关于角色特征的页面,但我看不出这些是如何使用的?他们是否替换了string.h函数? std::string似乎没有使用它们,例如length()上的std::string方法未使用字符特征length()方法。为什么角色特征存在并且它们是否曾在实践中使用过?

1 个答案:

答案 0 :(得分:153)

字符特征是流和字符串库中非常重要的组件,因为它们允许流/字符串类从的逻辑中分离存储的字符的逻辑应该对这些角色进行操作。

首先,默认字符特征类char_traits<T>在C ++标准中广泛使用。例如,没有名为std::string的类。相反,有一个类模板std::basic_string,如下所示:

template <typename charT, typename traits = char_traits<charT> >
    class basic_string;

然后,std::string被定义为

typedef basic_string<char> string;

同样,标准流定义为

template <typename charT, typename traits = char_traits<charT> >
    class basic_istream;

typedef basic_istream<char> istream;

那么为什么这些课程的结构如此?我们为什么要使用奇怪的traits类作为模板参数?

原因是在某些情况下我们可能想要一个像std::string一样的字符串,但是有一些稍微不同的属性。一个典型的例子是,如果你想以一种忽略大小写的方式存储字符串。例如,我可能想要创建一个名为CaseInsensitiveString的字符串,以便我可以拥有

CaseInsensitiveString c1 = "HI!", c2 = "hi!";
if (c1 == c2) {  // Always true
    cout << "Strings are equal." << endl;
}

也就是说,我可以有一个字符串,其中只有两个字符串区别于它们的区分大小写才相等。

现在,假设标准库作者设计的字符串不使用特征。这意味着我在标准库中有一个非常强大的字符串类,在我的情况下完全没用。我无法重用此字符串类的大部分代码,因为比较总是会影响我希望它们如何工作。但是通过使用traits,实际上可以重用驱动std::string的代码来获取不区分大小写的字符串。

如果您提取C ++ ISO标准的副本并查看字符串的比较运算符如何工作的定义,您将看到它们都是根据compare函数定义的。该函数又通过调用

来定义
traits::compare(this->data(), str.data(), rlen)

其中str是您要比较的字符串,rlen是两个字符串长度中较小的一个。这实际上非常有趣,因为它意味着compare的定义直接使用由指定为模板参数的traits类型导出的compare函数!因此,如果我们定义一个新的traits类,然后定义compare以便它不区分大小写地比较字符,我们可以构建一个行为与std::string类似的字符串类,但是对不区分大小写的事情进行处理! / p>

这是一个例子。我们继承自std::char_traits<char>以获取我们不写的所有函数的默认行为:

class CaseInsensitiveTraits: public std::char_traits<char> {
public:
    static bool lt (char one, char two) {
        return std::tolower(one) < std::tolower(two);
    }

    static bool eq (char one, char two) {
        return std::tolower(one) == std::tolower(two);
    }

    static int compare (const char* one, const char* two, size_t length) {
        for (size_t i = 0; i < length; ++i) {
            if (lt(one[i], two[i])) return -1;
            if (lt(two[i], one[i])) return +1;
        }
        return 0;
    }
};

(注意我这里也定义了eqlt,它们分别比较了相等和小于的字符,然后根据这个函数定义了compare

现在我们有了这个traits类,我们可以将CaseInsensitiveString简单地定义为

typedef std::basic_string<char, CaseInsensitiveTraits> CaseInsensitiveString;

瞧!我们现在有一个字符串,可以区分大小写!

当然,除此之外还有其他原因可以使用特征。例如,如果要定义使用固定大小的某些基础字符类型的字符串,则可以对该类型进行专门化char_traits,然后从该类型创建字符串。例如,在Windows API中,类型TCHAR可以是窄字符或宽字符,具体取决于您在预处理期间设置的宏。然后,您可以通过编写

TCHAR中创建字符串
typedef basic_string<TCHAR> tstring;

现在你有一串TCHAR s。

在所有这些示例中,请注意我们刚刚定义了一些traits类(或使用已存在的类)作为某个模板类型的参数,以便获取该类型的字符串。这一点的全部意义在于basic_string作者只需要指定如何使用特征,我们神奇地可以让他们使用我们的特征而不是默认值来获得具有某些细微差别或怪癖的字符串而不是默认的一部分字符串类型。

希望这有帮助!

编辑:正如@phooji所指出的,这个特征的概念不仅仅是由STL使用,也不是特定于C ++。作为一个完全无耻的自我推销,不久前我写了an implementation of a ternary search tree(一种基数树described here),它使用特征来存储任何类型的字符串,并使用客户希望它们存储的任何比较类型。如果你想看一个在实践中使用它的例子,这可能是一个有趣的读物。

编辑:针对您std::string未使用traits::length的说法,事实证明它在某些地方确实存在。最值得注意的是,当您从std::string C样式字符串构造char*时,通过在该字符串上调用traits::length来派生字符串的新长度。似乎traits::length主要用于处理C风格的字符序列,它们是C ++中字符串的“最小公分母”,而std::string用于处理任意内容的字符串。