В чем смысл черт характера STL?


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

1 70

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 это строка, которую вы сравниваете с and 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 автор просто должен указать, как использовать черты характера и мы волшебным образом можно заставить их использовать наши черты, а не по умолчанию, чтобы получить строки, которые имеют некоторые нюансы или причуды не являются частью типа строки по умолчанию.

надеюсь, что это помогает!

EDIT: как указал @phooji, это понятие признаков не просто используется STL, но и не является специфичным для C++. Как совершенно бесстыдная самореклама, некоторое время назад я написал реализация троичного дерева поиска (тип корня дерева описано здесь), который использует признаки для хранения строк любого типа и использует любой тип сравнения, который клиент хочет, чтобы они хранились. Это может быть интересно прочитать, если вы хотите увидеть пример того, где это используется на практике.

EDIT: в ответ на ваше утверждение, что std::string не использовать traits::length, оказывается, что это делает в нескольких местах. Особенно, когда вы строите std::string С char* строка в стиле C, новая длина строки получается путем звоню traits::length на эту строку. Кажется, что traits::length используется в основном для работы с последовательностями символов в стиле C, которые являются "наименее общим знаменателем" строк в C++, в то время как std::string используется для работы со строками произвольной содержание.