В чем смысл черт характера STL? - PullRequest
75 голосов
/ 16 марта 2011

Я заметил, что в моей копии ссылки SGI STL есть страница о чертах характера, но я не вижу, как они используются? Они заменяют функции string.h? Кажется, они не используются std::string, например метод length() в std::string не использует метод черт характера length(). Почему существуют черты характера, и используются ли они когда-либо на практике?

1 Ответ

154 голосов
/ 16 марта 2011

Символьные характеристики являются чрезвычайно важным компонентом библиотек потоков и строк, поскольку они позволяют классам потоков / строк отделить логику , какие символы хранятся , от логики , какие манипуляциидолжны выполняться с этими символами.

Для начала, класс черт характера по умолчанию, 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;

Так почему же эти классы структурированы так, как они есть?Почему мы должны использовать класс странных признаков в качестве аргумента шаблона?

Причина в том, что в некоторых случаях нам может потребоваться строка, похожая на std::string, но с некоторыми немного другими свойствами.Один классический пример этого - если вы хотите хранить строки таким образом, чтобы игнорировать регистр.Например, я мог бы захотеть сделать строку с именем CaseInsensitiveString так, чтобы я мог иметь

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

То есть я могу иметь строку, в которой две строки, отличающиеся только чувствительностью к регистру, сравниваются равными.

Теперь предположим, что авторы стандартной библиотеки разработали строки без использования черт.Это означало бы, что в стандартной библиотеке у меня был бы очень мощный строковый класс, который был бы совершенно бесполезен в моей ситуации.Я не мог повторно использовать большую часть кода для этого строкового класса, так как сравнения всегда работали против того, как я хотел, чтобы они работали.Но используя черты, на самом деле можно повторно использовать код, который управляет std::string, чтобы получить строку без учета регистра.

Если вы откроете копию стандарта C ++ ISO и посмотрите на определение того, какОператоры сравнения строк работают, вы увидите, что все они определены в терминах функции compare.Эта функция, в свою очередь, определяется вызовом

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

, где str - строка, с которой вы сравниваете, а rlen - меньшая из двух длин строки.Это на самом деле довольно интересно, потому что это означает, что определение compare напрямую использует функцию compare, экспортируемую по типу признаков, указанному в качестве параметра шаблона!Следовательно, если мы определим новый класс признаков, а затем определим compare, чтобы он сравнивал символы без учета регистра, мы можем создать строковый класс, который ведет себя так же, как std::string, но обрабатывает вещи без учета регистра!

Вот пример.Мы наследуем от 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;
    }
};

(Обратите внимание, что я также определил eq и lt здесь, которые сравнивают символы дляравенство и меньше, соответственно, а затем определено compare в терминах этой функции).

Теперь, когда у нас есть этот класс черт, мы можем определить CaseInsensitiveString тривиально как

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

И вуаля!Теперь у нас есть строка, которая обрабатывает все без учета регистра!

Конечно, есть и другие причины для использования черт.Например, если вы хотите определить строку, которая использует некоторый базовый тип символов фиксированного размера, вы можете специализировать char_traits для этого типа, а затем создавать строки из этого типа.Например, в Windows API есть тип TCHAR, который является либо узким, либо широким символом, в зависимости от того, какие макросы вы установили во время предварительной обработки.Затем вы можете сделать строки из TCHAR s, написав

typedef basic_string<TCHAR> tstring;

И теперь у вас есть строка TCHAR s.

Во всех этих примерах обратите внимание, что мы простоопределил некоторый класс признаков (или использовал уже существующий) в качестве параметра для некоторого типа шаблона, чтобы получить строку для этого типа.Весь смысл в том, что автору basic_string нужно просто указать, как использовать черты, и мы волшебным образом можем заставить их использовать наши черты, а не значения по умолчанию, чтобы получить строки, которые имеют некоторый нюанс или причуду, не являющуюся частью строкового типа по умолчанию..

Надеюсь, это поможет!

РЕДАКТИРОВАТЬ : Как указывало @phooji, это понятие признаков используется не только STL, но и не специфично для C ++. Как бесстыдная самореклама, некоторое время назад я написал реализацию троичного дерева поиска (тип описанного здесь базового дерева ), который использует черты для хранения строк любого типа. и используя тот тип сравнения, который клиент хочет, чтобы он сохранил. Это может быть интересно прочитать, если вы хотите увидеть пример, где это используется на практике.

РЕДАКТИРОВАТЬ : В ответ на ваше заявление о том, что std::string не использует traits::length, оказывается, что оно используется в нескольких местах. В частности, когда вы создаете std::string из строки char* C-стиля, новая длина строки получается путем вызова traits::length для этой строки. Кажется, что traits::length используется главным образом для работы с последовательностями символов в стиле C, которые являются «наименее общим знаменателем» строк в C ++, тогда как std::string используется для работы со строками произвольного содержимого.

...