Методов find в классе HashImpl, реализующем хранение произвольных данных по хеш-ключам, есть два:
1) const Item* find (KeyType key) const
2) Item* find (KeyType key)
Первый вариант будет вызываться для константного объекта, а второй - для неконстантного. Проблема следующая: код этих методов звучит не просто как "return & mHash[key]", а реализует какой-никакой, а все же алгоритм поиска. Да и хешей там два - первичный и вторичный, что еще немножко усложняет ситуацию. Внутри методов используются всякие вспомогательные указатели на внутренние данные класса, которые в константном методе должны быть константными, а в неконстантных - соответственно, неконстантными.
Отсюда формулируем вопрос: как избежать дублирования кода для константной (const) и неконстантной версий одного и того же метода, реализующего нетрививальный алгоритм? Примечание: константный объект может пользоваться только методами, объявленными с квалификатором const, и не может использовать неконстантные указатели и неконстантные ссылки на свои данные - в частности, не может их возвращать.
Нарытые варианты решения:
1. Вызывать константный метод из неконстантного.
Чем плох: константный метод вернет константный указатель, с которого затем придется снимать константность, используя const_cast
2. Объявлять хеш-таблицы как mutable, чтобы иметь право изменять их в константном методе
Чем плох: mutable - грязный хак. Если программист серьезно размышляет об использовании mutable - ему надо выйти проветриться (цитатирую Мишу Нореля). Короче, тоже не наш метод.
3. Использовать неконстантные указатели "через один указатель"
Объясняю популярно. Если в классе Foo есть поле mData типа Bar, то из константного метода нельзя сделать return & mData.getItem(key). Но зато если эту же mData объявить как Bar*, выделяя память для него в конструкторе, то из константной функции можно сделать return & (mData->getItem(key)).
Чем плох: не удовлетворяет требованиям к классу: необходимо использовать статическую инициализацию. И класс-то вовсе не класс, а шаблон, один из параметров которого - размер хеш-таблицы. Опять облом.
4. Хранить рядом с данными указатель на них, и возвращать item через него (см. метод 3)
Ну, типа объявить в классе Foo два поля: Bar mData и Bar* mDataPtr, присвоив последнему mDataPtr = & mData в конструкторе.
Чем плох: См., во-первых, метод 1. Если объект будет размещен в защищенной от записи памяти, то при записи по указателю mDataPtr будет жопа. Во-вторых, sizeof всех объектов увеличится на 4 байта. Если объектов будет миллион, то мы проебем 4 метра памяти, что для целевой платформы может быть нехорошо.
5. Написать еще одну константную функцию для поиска
Да-да, пишем третью функцию:
void findHadler (KeyType key, KeyType & resKey) const
Такой метод можно вызывать из обоих версий find. Он вернет ключ, по которому хранится искомый item. А потом константный find может вернуть const Item* p = & mData[resKey], а неконстантный - Item* p = & mData[resKey].
Чем плох: Добавляются лишние вызовы, немножко падает производительность, дыр-пыр-тыр, бла-бла-бла. Все это, по сравнению с проблемами предыдущих вариантов - суть мелочи, поэтому я остановился именно тут.
Итого: я не люблю C++, среди много-прочего, как раз за такие вот мансы. Ну, типа, придумали мы слово const - и теперь ловим сопутствующие проблемы. Зато (sic!) мы получаем неибацца свободу выбора решений таких проблем...
А свободу, как известно, не купишь :)
Чел, mutable - это не грязный хак, а возможность разделить ценные данные класса и служебную информацию.
Анонимный
17 июня 2009 г. в 15:50Главная проблема с mutable, как я ее вижу, состоит в следующем. Когда мы объявляем метод константным, мы декларируем, что внутри этого метода мы не собираемся изменять состояние объекта. Mutable - довольно грязный способ не оправдать ожидания и все-таки изменить объект. Нарушается т.н. принцип "наименьшего удивления" (http://benpryor.com/blog/2006/06/29/api-design-the-principle-of-least-surprise/): ожидали, что объект не изменится, а он изменился..
Подробнее про mutable имхо хорошо написано здесь: http://www.highprogrammer.com/alan/rants/mutable.html
Sergey Borodavkin
18 июня 2009 г. в 10:14а вы прикольный!
const это слишком сильное утверждение для вашего примера функции.
то есть вы говорите следующее. а дай-ка я сделаю сильное утверждение для функции, и попытаюсь ее реализовать функциями или методами со слабыми утверждениями! Эко я на грабель налечу!
ну налетели вы на грабель, как тому положено. и сделали выводов об этом полезном инструменте...
Анонимный
20 июля 2009 г. в 19:26Грабель, говорите? Работа в аутсорсинговой компании накладывает ограничения на полет творчества. Короче - const там уже стоял, и я его там не придумал))
Хотя смысл двух (const и не-const) методов, я думаю, ясен: искать нужно уметь как в неконстантном, так и в константном Hash-е.
Sergey Borodavkin
21 июля 2009 г. в 09:14void findHadler (KeyType key, KeyType & resKey) const
Наверное имелось в виду Handler.
А в остальном очень полезно, спасибо!
Анонимный
13 февраля 2011 г. в 11:04