В чем смысл черт характера STL?
Я заметил, что в моей копии ссылки SGI STL есть страница о чертах характера, но я не вижу, как они используются? Они заменяют функции string.h? Кажется, они не используются std::string
например, length()
метод на std::string
не использует черты характера length()
метод. Почему существуют Черты характера и используются ли они на практике?
1 ответ
Черты характера являются чрезвычайно важным компонентом библиотек потоков и строк, поскольку они позволяют классам потоков / строк отделить логику того, какие символы хранятся, от логики того, какие манипуляции должны выполняться над этими символами.
Для начала, класс черт характера по умолчанию, 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
с написанием
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
используется для работы со строками произвольного содержимого.