Подтвердить что ты не робот

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

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

4b9b3361

Ответ 1

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

Начнем с того, что класс символов символов по умолчанию char_traits<T> используется широко в стандарте С++. Например, нет класса 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, чтобы получить строку, не учитывающую регистр.

Если вы подтянете копию стандарта С++ 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 просто нужно указать, как использовать черты, и мы магически можем заставить их использовать наши черты, а не по умолчанию, чтобы получить строки, которые имеют некоторый нюанс или quirk, не являющийся частью типа строки по умолчанию.

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

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

ИЗМЕНИТЬ: В ответ на ваше утверждение, что std::string не использует traits::length, оказывается, что он делает это в нескольких местах. Прежде всего, когда вы строите строку std::string из строки char* C-стиля, новая длина строки выводится путем вызова traits::length в этой строке. Похоже, что traits::length используется в основном для обработки последовательностей символов C-стиля, которые являются "наименее общим знаменателем" строк в С++, а std::string используется для работы со строками произвольного содержимого.