C++17 STL Стандартная библиотека шаблонов — страница 73 из 119

собственный класс 
string
. Такая идея редко хороша, поскольку не так просто безопасно обработать строки. К счастью, класс
std::string
— лишь специализированное ключевое слово шаблонного класса
std::basic_string
. Данный класс содержит все сложные средства обработки памяти, но не навязывает никаких правил, как копировать строки, сравнивать их и т.д. Эти правила импортируются в
basic_string
, для чего принимается шаблонный параметр, который содержит класс
traits
.

В этом примере мы увидим, как создавать собственные классы типажей и, соответственно, как создавать пользовательские строки, не реализуя повторно все их возможности.


Как это делается

В этом примере мы реализуем два разных пользовательских строковых класса:

lc_string
и
ci_string
. Первый создает на основе любых входных данных строки в нижнем регистре. Второй строки не преобразует, но может выполнить сравнение строк независимо от регистра.


1. Включим несколько необходимых заголовочных файлов, а затем объявим об использовании пространства имен

std
по умолчанию:


#include 

#include 

#include 


using namespace std;


2. Затем снова реализуем функцию

std::tolower
, которая уже определена в
. Уже существующая функция работает хорошо, но она не имеет модификатора
constexpr
. Однако отдельные строковые функции имеют этот модификатор, начиная с C++17, и мы хотим иметь возможность использовать их для нашего собственного строкового класса-типажа. Функция соотносит символы в верхнем регистре с символами в нижнем регистре и оставляет другие символы неизменными:


static constexpr char tolow(char c) {

  switch (c) {

    case 'A'...'Z': return c - 'A' + 'a';

    default: return c;

  }

}


3. Класс

std::basic_string
принимает три шаблонных параметра: тип основного символа, класс-типаж символа и тип
allocator
. В этом разделе мы изменяем только класс-типаж символа, поскольку он определяет поведение строк. Для повторной реализации только того, что должно отличаться от типичного поведения строк, мы явно наследуем от стандартного класса-типажа:


class lc_traits : public char_traits {

public:


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

tolow
. Она имеет модификатор
constexpr
, именно поэтому мы и реализовали самостоятельно функцию
tolow
с таким же модификатором
constexpr
:


  static constexpr

  void assign(char_type& r, const char_type& a ) {

    r = tolow(a);

  }


5. Еще одна функция обрабатывает копирование целой строки в отдельный участок памяти. Мы используем вызов

std::transform
, чтобы скопировать все символы из исходной строки в строку — место назначения, и в то же время соотносим каждый символ с его версией в нижнем регистре:


  static char_type* copy(char_type* dest,

                         const char_type* src,

                         size_t count) {

    transform(src, src + count, dest, tolow);

    return dest;

  }

};


6. Еще один типаж помогает создать строковый класс, эффективно преобразующий строки в нижний регистр. Напишем еще один типаж, который не изменяет полученную строку, но позволяет выполнять сравнение строк независимо от регистра. Снова унаследуем от существующего стандартного класса-типажа для символов и в этот раз переопределим некоторые члены функции:


class ci_traits : public char_traits {

public:


7. Функция

eq
указывает, равны ли два символа. Реализуем такую же функцию, но будем сравнивать версии символов в нижнем регистре. При использовании этой функции символ
'A'
равен символу
'a'
.


  static constexpr bool eq(char_type a, char_type b) {

    return tolow(a) == tolow(b);

}


8. Функция

lt
указывает, меньше ли значение
a
значения
b
. Применим корректный логический оператор, но только после того, как оба символа будут преобразованы к нижнему регистру:


  static constexpr bool lt(char_type a, char_type b) {

    return tolow(a) < tolow(b);

}


9. Последние две функции работали для посимвольного ввода, а следующие две будут работать для строк. Функция compare работает по аналогии со старой функцией

strncmp
. Она возвращает значение
0
при условии, что обе строки равны от начала до позиции, указанной в переменной
count
. Если они отличаются, то функция возвращает отрицательное или положительное число, которое указывает, какая из строк была меньше с точки зрения лексикографии. Определение разности между обоими символами в каждой позиции должно, конечно же, происходить для символов в нижнем регистре. Положительный момент заключается в том, что весь код цикла является частью функции с модификатором
constexpr
, начиная с C++14.


  static constexpr int compare(const char_type* s1,

                               const char_type* s2,

                               size_t count) {

  for (; count; ++s1, ++s2, --count) {

    const char_type diff (tolow(*s1) - tolow(*s2));

    if (diff < 0) { return -1; }

    else if (diff > 0) { return +1; }

  }

  return 0;

}


10. Последняя функция, которую нужно реализовать для нашего строкового класса, не зависящего от регистра, — это функция

find
. Для заданной входной строки
p
и ее длины
count
она определяет позицию символа
ch
. Затем возвращает указатель на первое включение данного символа или
nullptr
, если такого символа нет. Сравнение в этой функции должно выполняться с использованием функции
tolow
, чтобы поиск не зависел от регистра. К сожалению, мы не можем применить функцию
std::find_if
, поскольку она не имеет модификатора
constexpr
, нужно писать цикл самостоятельно.


  static constexpr

  const char_type* find(const char_type* p,

                        size_t count,

                        const char_type& ch) {

    const char_type find_c {tolow(ch)};

    for (; count != 0; --count, ++p) {

      if (find_c == tolow(*p)) { return p; }

    }

    return nullptr;

  }

};


11. О’кей, с типажами мы закончили. Теперь можно определить два новых строковых типа.

lc_string
означает lower-case string (строка в нижнем регистре).
ci_string
расшифровывается как case-insensitive string (строка, не зависящая от регистра). Оба класса отличаются от класса
std::string
своими классами-типажами для символов:


using lc_string = basic_string;

using ci_string = basic_string