В чем смысл черт характера STL?
Я замечаю, что в моей копии ссылки SGI STL есть страница о чертах характера, но я не вижу, как они используются? Они заменяют строку.функции h? Они, кажется, не используются std::string, например,length() метод on std::string не использует черты характера length() метод. Почему существуют черты характера и используются ли они на практике?
1 ответ:
черты характера являются чрезвычайно важным компонентом библиотек потоков и строк, поскольку они позволяют классам stream / string отделять логику какие символы хранятся логика какие манипуляции следует выполнять с этими символами.
для начала, класс черт характера по умолчанию,
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; }то есть у меня может быть строка, где две строки, отличающиеся только чувствительностью к регистру, сравниваются равными.
теперь предположим,что авторы стандартной библиотеки разработали строки без использования признаков. Это означало бы, что в стандартной библиотеке у меня будет очень мощный класс string это было совершенно бесполезно в моей ситуации. Я не мог повторно использовать большую часть кода для этого класса string, так как сравнения всегда будут работать против того, как я хотел, чтобы они работали. Но с помощью черт, это на самом деле можно повторно использовать код, который управляет
std::stringчтобы получить строку без учета регистра.если вы вытащите копию стандарта ISO C++ и посмотрите на определение того, как работают операторы сравнения строк, вы увидите, что все они определены в терминах
compareфункция. Эта функция в свою очередь определяется вызовомtraits::compare(this->data(), str.data(), rlen)здесь
strэто строка, которую вы сравниваете с andrlenявляется меньшим из двух длин строк. Это на самом деле довольно интересно, потому что это означает, что определение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то есть либо узкий, либо широкий характер в зависимости от того, какие макросы вы устанавливаете во время предварительной обработки. Затем вы можете сделать строки изTCHARs, написавtypedef basic_string<TCHAR> tstring;и теперь у вас есть строка
TCHARs.во всех этих примерах обратите внимание, что мы только что определили некоторый класс признаков (или использовали тот, который уже существовал) в качестве параметра для некоторого типа шаблона, чтобы получить строку для этого типа. Все дело в том, что
basic_stringавтор просто должен указать, как использовать черты характера и мы волшебным образом можно заставить их использовать наши черты, а не по умолчанию, чтобы получить строки, которые имеют некоторые нюансы или причуды не являются частью типа строки по умолчанию.надеюсь, что это помогает!
EDIT: как указал @phooji, это понятие признаков не просто используется STL, но и не является специфичным для C++. Как совершенно бесстыдная самореклама, некоторое время назад я написал реализация троичного дерева поиска (тип корня дерева описано здесь), который использует признаки для хранения строк любого типа и использует любой тип сравнения, который клиент хочет, чтобы они хранились. Это может быть интересно прочитать, если вы хотите увидеть пример того, где это используется на практике.
EDIT: в ответ на ваше утверждение, что
std::stringне использоватьtraits::length, оказывается, что это делает в нескольких местах. Особенно, когда вы строитеstd::stringСchar*строка в стиле C, новая длина строки получается путем звонюtraits::lengthна эту строку. Кажется, чтоtraits::lengthиспользуется в основном для работы с последовательностями символов в стиле C, которые являются "наименее общим знаменателем" строк в C++, в то время какstd::stringиспользуется для работы со строками произвольной содержание.