Физическое и логическое постоянство объектов. Модификатор mutable
Если мы будем оперировать например с преобразованием объекта-дату в строку, то мы столкнемся с проблемой сохранения строки во временном массиве, так как после возвращения из функции ту память, под которую мы выделили для строки в стеке будет считаться не занятой и может быть в любой момент перезаписана чем-либо другим. Решением этой проблемы будет использование либо глобального, либо статического локального буфера памяти, имеющего постоянное место хранения.
//тело класса
// Функция преобразования даты в строку
const char* ToString ( char _sep ) const
{
// Размещаем результирующую строку в статическом локальном массиве
static char tempBuf[ 11 ];
sprintf( tempBuf, "%d%c%d%c%d", m_Year, _sep, m_Month, _sep, m_Day );
return tempBuf;
}
//выведим на экран ОДНУ дату-объект
Date d( 2013, 5, 3 );
const char * str = d.ToString( '/' );
std::cout << str << std::endl;
Если же мы будем пытаться выводить два и более дат-объектов, то мы будем выводить на экран только последний созданный объект.
Решить указанную проблему можно размещением буфера для результирующей строки непосредственно в памяти объекта-даты. В таком случае каждый объект получил бы индивидуальный ни с кем не разделяемый буфер. Однако, такой подход ведет к противоречию условия неизменности объекта — с логической точки зрения преобразование даты в строку не должно изменять состояние объекта-даты, что подчеркнуто модификатором const, а физически состояние измениться заполнением буфера символов.
Для случаев, когда в классе часть переменных-членов не образует логической основы понятия, а носит лишь вспомогательный характер, некий низкоуровневый механизм реализации, предусмотрено ключевое слово mutable. Такой модификатор переменной-члена разрешает изменение состояния даже если это происходит внутри функции-члена с модификатором const.
//тело класса
// Буфер для строкового представления. Можно изменять даже в const-методах
mutable char m_tempBuf[ 10 ];
public:
// ...
// Функция преобразования даты в строку
const char* ToString ( char _sep ) const
{
// Размещаем результирующую строку в мутирующем буфере внутри объекта
sprintf( m_tempBuf, "%d%c%d%c%d", m_Year, _sep, m_Month, _sep, m_Day );
return tempBuf;
}
Представляется возможным повысить производительность операции ToString для класса Date, если избежать переформирования строкового представления при отсутствии изменения состояния данных о годе, месяце, дне. Для этого понадобится еще одна “мутирующая” переменная-член, означающая, что объект не менял своего состояния, соответственно, буфер содержит нужные символы:
Буфер для строкового представления. Можно изменять даже в const-методах
mutable char m_tempBuf[ 10 ];
// Флаг валидности содержимого буфера
mutable boolm_BufferValid;
Флаг устанавливается в значение false при создании объекта
Флаг также устанавливается в значение false при изменении состояния объекта
// Функция преобразования даты в строку
const char* ToString ( char _sep ) const
{
// Переформируем буфер только если он в неактуальном состоянии
if( ! m_BufferValid )
{
// Размещаем результирующую строку в мутирующем буфере внутри объекта
sprintf( m_tempBuf, "%d%c%d%c%d", m_Year, _sep, m_Month, _sep, m_Day );
// Буфер теперь актуален
m_Buffervalid = true;
}
return tempBuf;
}
Если таких “мутирующих” полей в классе появляется много, это скорее всего свидетельствует о необходимости выноса части состояния объекта в другой вспомогательный объект, отдельно отвечающий за физическую часть.
//тело класса
structStringRepr
{
charm_tempBuf[ 10 ];
boolm_isValid;
}
StringRepr* m_pStringRepr;
В целом, для объекта-даты такое решение является чересчур громоздким, однако такой подход вполне пригоден для более крупных объектов, обновление физической части которых занимает существенное время.
20. Класс std::string из стандартной библиотеки. Основная функциональность, способы применения. Особенности внутренней структуры.
Многие программисты, предпочитающие другие языки программирования, часто нарекают на сложность работы со строками в С++. Следует отметить, что эта критика, в основном, направлена на базовое представление строк в языке С в виде массива символов с нулевым завершителем. Действительно, в языке С, реализация вышеупомянутых операций несколько затруднительна, поскольку требует постоянного рассуждения о памяти для хранения символов, использования низкоуровневых функций наподобие strcpy и strcmp, расстановки нулевых завершителей вручную в ряде ситуаций.
Принципы объектно-ориентированного программирования, предоставляемые С++, позволяют эффективно сочетать потребности программистов в удобстве работы со строками вместе с производительностью низкоуровневого представления. В частности, стандартная библиотека предлагает для нужд работы со строками мощный готовый класс std::string, доступный в заголовочном файле <string>.
Этот класс определяет необходимые конструкторы, средства для копирования и перемещения, деструктор. Типичная реализация оптимизирует хранение символов таким образом, что для строк небольшого размера (до 16 символов), кои встречаются в большинстве программ наиболее часто, вообще не происходит динамического выделения памяти. Вместо этого реализация хранит символы в статическом буфере до тех пор, пока строка не увеличивается в размере до достаточно длинной. При дальнейшем росте, std::string выделяет динамическую память подобно векторам из дисциплины “Структуры и алгоритмы обработки данных”.
В связи с этим, объекты std::string можно свободно присваивать, передавать в качестве аргументов функций, возвращать из функций как результат, создавать временные объекты для формирования строк из фрагментов.
Класс std::string также интенсивно использует механизмы перегрузки операторов, в частности:
● оператор индексной выборки [] для обращения к конкретным символам строки;
● операторы +, += для конкатенирования строк;
● операторы сравнения (==, !=, <, <=, >, >=);
● операторы <<, >> для ввода вывода через потоки (консоль, файлы).
Операторы ввода-вывода особенно удобны, поскольку программисту не нужно заботиться о памяти для вводимых строк, т.к. библиотечные средства выделят ее самостоятельно.
Также, класс std::string предлагает широкий набор вспомогательных методов, полезных для типичных задач со строками. Среди наиболее часто используемых следующие методы:
● size, length - методы определения длины;
● empty - метод определения пустоты строки;
● reserve, capacity - средства резервирования места для хранения символов заранее;
● clear - метод очистки строки;
● insert/erase - вставка/удаление фрагментов;
● replace - замена фрагментов на другие;
● find, rfind, find_first_of, find_last_of - поиск фрагментов;
● substr - получение подстроки;
● с_str - преобразование к строке в стиле языка С (указатель на const char * ).