Рассмотрите следующую функцию членства C++:
size_t size() const
{
boost::lock_guard<boost::mutex> lock(m_mutex);
return m_size;
}
Намерение здесь не состоит в том, чтобы синхронизировать доступ к переменной члена парламента, не занимающего официального поста m_size
, но только удостовериться, что вызывающая сторона получает допустимое значение для m_size. Цель состоит в том, чтобы препятствовать тому, чтобы функция возвратилась m_size
в то же самое время, когда некоторый другой поток изменяет m_size
.
Но есть ли какое-либо потенциальное состояние состязания, вовлеченное в вызывание этой функции? Я не уверен, соответствует ли блокировка стиля RAII здесь для защиты от состояния состязания. Предположим, что деструктор блокировки называют, прежде чем возвращаемое значение функции продвинуто на стек?
Я должен был бы сделать что-то как следующее для гарантии потокобезопасности?
size_t size() const
{
size_t ret;
{
boost::lock_guard<boost::mutex> lock(m_mutex);
ret = m_size;
}
return ret;
}
Обе конструкции из ваших примеров будут делать то, что вы ищете. Следующая информация из стандарта поддерживает поведение, которое вы ищете (даже в вашем 1-м примере):
12.4 / 10 Деструкторы:
Деструкторы вызываются неявно ... для сконструированного объекта с автоматической продолжительностью хранения (3.7 .2) при выходе из блока, в котором создан объект.
И, 6.6 / 2 Операторы перехода (из которых return
- один):
При выходе из области видимости (независимо от того, выполнено ли это) деструкторы (12.4) вызываются для всех сконструированных объектов с автоматическим сохранением. duration (3.7.2) (именованные объекты или временные объекты), объявленные в этой области, в порядке, обратном их объявлению. Передача из цикла, из блока или обратно через инициализированную переменную с автоматической продолжительностью хранения включает в себя уничтожение переменных с автоматической продолжительностью хранения, которые находятся в области видимости в точке передачи, но не в точке передачи.
Итак, в момент возврата
переменная lock
находится в области видимости, и поэтому dtor не был вызван. После выполнения return
вызывается dtor для переменной lock
(таким образом снимая блокировку).
Ваш первый вариант безопасен, однако вы не можете рассчитывать на то, что это возвращаемое значение будет непротиворечивым в течение любого периода времени. Я имею в виду, например, что не используйте это возвращаемое значение в цикле for для итерации по каждому элементу, потому что реальный размер может измениться сразу после возврата.
В основном вы можете думать об этом так: требуется копия возвращаемого значения, иначе будет вызван деструктор, что может привести к повреждению того, что было возвращаемым значением до его возврата.
Деструктор вызывается после оператора return. Возьмем эквивалентный пример:
#include <assert.h>
class A
{
public:
~A()
{
x = 10;
}
int x;
};
int test()
{
A a;
a.x = 5;
return a.x;
}
int main(int argc, char* argv[])
{
int x = test();
assert(x == 5);
return 0;
}
Ваш исходный код в порядке - деструктор будет вызван после сохранения возвращаемого значения. Это тот самый принцип, на котором работает RAII!