Скрытый вопрос: куда вы помещаете мьютекс, защищающий ваш класс?
В качестве резюме, скажем, вы хотите прочитать содержимое объекта, который защищен мьютексом.
Метод «read» должен быть семантически «const», потому что он не изменяет сам объект.Но чтобы прочитать значение, вам нужно заблокировать мьютекс, извлечь его, а затем разблокировать мьютекс, то есть сам мьютекс должен быть изменен, то есть сам мьютекс не может быть «константным».
Еслимьютекс внешний
Тогда все в порядке.Объект может быть «const», и мьютекс не должен быть:
Mutex mutex ;
int foo(const Object & object)
{
Lock<Mutex> lock(mutex) ;
return object.read() ;
}
ИМХО, это плохое решение, потому что любой может повторно использовать мьютекс для защиты чего-то другого.Включая тебя.На самом деле, вы предадите себя, потому что, если ваш код достаточно сложен, вы просто запутаетесь в том, что именно защищает тот или иной мьютекс.
Я знаю: я стал жертвой этой проблемы.
Если мьютекс является внутренним
В целях инкапсуляции вы должны располагать мьютекс как можно ближе к объекту, который он защищает.
Обычно вы пишете класс смьютекс внутри.Но рано или поздно вам понадобится защитить какую-то сложную структуру STL или что-то написанное другим без мьютекса внутри (что хорошо).
Хороший способ сделать это - получить исходныйобъект с наследующим шаблоном, добавляющий функцию мьютекса:
template <typename T>
class Mutexed : public T
{
public :
Mutexed() : T() {}
// etc.
void lock() { this->m_mutex.lock() ; }
void unlock() { this->m_mutex.unlock() ; } ;
private :
Mutex m_mutex ;
}
Таким образом, вы можете написать:
int foo(const Mutexed<Object> & object)
{
Lock<Mutexed<Object> > lock(object) ;
return object.read() ;
}
Проблема в том, что он не будет работать, потому что object
является постоянными объект блокировки вызывает неконстантные методы lock
и unlock
.
Дилемма
Если вы считаете, что const
ограничено побитовыми объектами const, то вы 'перевернут и должен вернуться к «решению внешнего мьютекса».
Решение состоит в том, чтобы признать, что const
является более семантическим классификатором (как и volatile
при использовании в качестве квалификатора метода классов).Вы скрываете тот факт, что класс не является полностью const
, но все же убедитесь, что предоставили реализацию, которая держит обещание, что значимые части класса не будут изменены при вызове const
метода.
Затем вы должны объявить свой изменяемый мьютекс и методы блокировки / разблокировки const
:
template <typename T>
class Mutexed : public T
{
public :
Mutexed() : T() {}
// etc.
void lock() const { this->m_mutex.lock() ; }
void unlock() const { this->m_mutex.unlock() ; } ;
private :
mutable Mutex m_mutex ;
}
Внутреннее решение мьютекса является хорошим ИМХО: наличие объектов, объявленных один рядом с другим в одной руке, ис другой стороны, объединение их обоих в обертку - это то же самое.
Но у агрегации есть следующие плюсы:
- Это более естественно (вы блокируетеобъект до доступа к нему)
- Один объект, один мьютекс.Поскольку стиль кода заставляет вас следовать этому шаблону, он снижает риски взаимоблокировки, поскольку один мьютекс будет защищать только один объект (а не несколько объектов, которые вы на самом деле не помните), и один объект будет защищен только одним мьютексом (а немножественный мьютекс, который необходимо заблокировать в правильном порядке)
- Приведенный выше класс мьютекса можно использовать для любого класса
Так что держите свой мьютекс как можно ближе к объекту мьютекса(например, используя вышеупомянутую конструкцию Mutexed), и перейдите к квалификатору mutable
для мьютекса.
Edit 2013-01-04
Очевидно, у Херба Саттера есть та же точка зрения: его представлениео "новых" значениях const
и mutable
в C ++ 11 очень поучительно:
http://herbsutter.com/2013/01/01/video-you-dont-know-const-and-mutable/