Физическое и логическое постоянство объектов. Модификатор 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 * ).