После просмотра выступления Херба Саттера Вы не знаете, постоянный и изменчивый, Интересно, должен ли я всегда определять мутекс как изменяемый? Если да, я думаю, что то же самое верно для любого синхронизированного контейнера (например, tbb::concurrent_queue
)?
Немного предыстории: в своем выступлении он заявил, что const == mutable == thread-safe, и std::mutex
по определению является потокобезопасным.
Есть также связанный вопрос о разговоре, Означает ли const поточно-ориентированный в C ++ 11.
Редактировать:
Вот, Я нашел связанный вопрос (возможно, дубликат). Это было задано до C ++ 11, хотя. Может быть, это имеет значение.
Нет. Однако большую часть времени они будут.
Хотя полезно думать о const
как «потокобезопасный» и mutable
как «(уже) поточно-безопасный», const
все еще фундаментально связано с понятием обещания «я не буду изменять это значение». Так будет всегда.
У меня долгий ход мыслей, так что терпите меня.
В моем собственном программировании я положил const
везде. Если у меня есть ценность, это плохо менять ее, если я не скажу, что хочу. Если вы попытаетесь преднамеренно изменить const-объект, вы получите ошибку во время компиляции (это легко исправить, и вы не получите результата!). Если вы случайно измените неконстантный объект, вы получите ошибку программирования во время выполнения, ошибку в скомпилированном приложении и головную боль. Так что лучше ошибиться на прежней стороне и сохранить вещи const
,
Например:
bool is_even(const unsigned x)
{
return (x % 2) == 0;
}
bool is_prime(const unsigned x)
{
return /* left as an exercise for the reader */;
}
template <typename Iterator>
void print_special_numbers(const Iterator first, const Iterator last)
{
for (auto iter = first; iter != last; ++iter)
{
const auto& x = *iter;
const bool isEven = is_even(x);
const bool isPrime = is_prime(x);
if (isEven && isPrime)
std::cout << "Special number! " << x << std::endl;
}
}
Почему типы параметров для is_even
а также is_prime
отмеченный const
? Потому что с точки зрения реализации изменение числа, которое я тестирую, было бы ошибкой! Зачем const auto& x
? Потому что я не собираюсь менять это значение, и я хочу, чтобы компилятор кричал на меня, если я это сделаю. То же самое с isEven
а также isPrime
: результат этого теста не должен меняться, поэтому применяйте его.
Конечно const
функции-члены просто способ дать this
тип формы const T*
, Там написано «было бы ошибкой в реализации, если бы я сменил некоторых своих членов».
mutable
говорит «кроме меня». Отсюда и «старое» понятие «логически постоянное». Рассмотрим общий вариант использования, который он дал: член мьютекса. Вы необходимость заблокировать этот мьютекс, чтобы убедиться, что ваша программа корректна, поэтому вам нужно ее изменить. Вы не хотите, чтобы функция была неконстантной, потому что было бы ошибкой изменять любой другой элемент. Итак, вы делаете это const
и пометить мьютекс как mutable
,
Ничто из этого не имеет отношения к безопасности потока.
Я думаю, что это слишком далеко, чтобы сказать, что новые определения заменяют старые идеи, данные выше; они просто дополняют его с другой точки зрения безопасности потоков.
Теперь Трава дает точку зрения, что если у вас есть const
функции, они должны быть поточно-ориентированными для безопасного использования стандартной библиотекой. Как следствие этого, единственные участники, которых вы действительно должны пометить как mutable
те, которые уже потокобезопасны, потому что они могут быть изменены с const
функция:
struct foo
{
void act() const
{
mNotThreadSafe = "oh crap! const meant I would be thread-safe!";
}
mutable std::string mNotThreadSafe;
};
Итак, мы знаем, что потокобезопасные вещи Можно быть отмеченным как mutable
Вы спросите: они должны быть?
Я думаю, что мы должны рассмотреть оба взгляда одновременно. С новой точки зрения Херб, да. Они потокобезопасны, поэтому их не нужно связывать с постоянством функции. Но только потому, что они Можно безопасно быть освобожденным от ограничений const
не значит, что они должны быть. Мне все еще нужно подумать: будет ли ошибка в реализации, если я изменю этот элемент? Если так, то это не должно быть mutable
!
Здесь есть проблема гранулярности: некоторым функциям может понадобиться изменить mutable
член, а другие нет. Это все равно что хотеть, чтобы только у некоторых функций был доступ в виде друзей, но мы можем дружить только со всем классом. (Это проблема языкового дизайна.)
В этом случае вы должны ошибиться на стороне mutable
,
Херб говорил слишком слабо, когда он дал const_cast
Пример объявил это безопасным. Рассматривать:
struct foo
{
void act() const
{
const_cast<unsigned&>(counter)++;
}
unsigned counter;
};
Это безопасно в большинстве случаев, кроме случаев, когда foo
сам объект const
:
foo x;
x.act(); // okay
const foo y;
y.act(); // UB!
Это рассматривается в другом месте на SO, но const foo
, подразумевает counter
член также const
и модифицирование const
объект неопределенного поведения.
Вот почему вы должны ошибиться на стороне mutable
: const_cast
не совсем дает вам такие же гарантии. Имел counter
был отмечен mutable
, это не было бы const
объект.
Хорошо, так что если нам это нужно mutable
в одном месте нам это нужно везде, и нам просто нужно быть осторожным в тех случаях, когда мы этого не делаем. Конечно, это означает, что все потокобезопасные элементы должны быть помечены mutable
затем?
Ну, нет, потому что не все поточно-ориентированные члены предназначены для внутренней синхронизации. Самый тривиальный пример — это своего рода класс-оболочка (не всегда лучшая практика, но они существуют):
struct threadsafe_container_wrapper
{
void missing_function_I_really_want()
{
container.do_this();
container.do_that();
}
const_container_view other_missing_function_I_really_want() const
{
return container.const_view();
}
threadsafe_container container;
};
Здесь мы оборачиваем threadsafe_container
и предоставление другой функции-члена, которую мы хотим (было бы лучше в качестве бесплатной функции на практике). Нет необходимости mutable
здесь, правильность со старой точки зрения совершенно превосходит: в одной функции я изменяю контейнер и это нормально, потому что я не сказал, что не буду (опуская const
), а в другом я не изменяю контейнер и убедитесь, что я сдерживаю это обещание (опуская mutable
).
Я думаю, что Херб спорит в большинстве случаев, когда мы будем использовать mutable
мы также используем какой-то внутренний (потокобезопасный) объект синхронизации, и я согласен. Поэтому его точка зрения работает большую часть времени. Но существуют случаи, когда я просто случаться иметь потокобезопасный объект и просто рассматривать его как еще один элемент; в этом случае мы возвращаемся к старому и фундаментальному использованию const
,
Я просто смотрел разговор и не совсем согласен с тем, что говорит Херб Саттер.
Если я правильно понимаю, его аргумент таков:
[res.on.data.races]/3
накладывает требование на типы, которые используются со стандартной библиотекой — неконстантные функции-члены должны быть поточно-ориентированными.
Следовательно const
эквивалентно потокобезопасному.
И если const
эквивалентно потокобезопасному, то mutable
должно быть эквивалентно «поверьте мне, даже неконстантные члены этой переменной являются поточно-ориентированными».
На мой взгляд, все три части этого аргумента ошибочны (а вторая часть критически ошибочна).
Проблема с 1
в том, что [res.on.data.races]
дает требования к типам в стандартной библиотеке, а не к типам, которые будут использоваться со стандартной библиотекой. Тем не менее, я думаю, что это разумно (но не совсем четко) интерпретировать [res.on.data.races]
а также предоставление требований к типам, которые будут использоваться со стандартной библиотекой, поскольку для реализации библиотеки было бы практически невозможно выполнить требование не изменять объекты посредством const
ссылки, если const
функции-члены могли изменять объекты.
критический проблема с 2
в то время как это правда (если мы примем 1
) тот const
должен подразумевать потокобезопасность, это не правда, что потокобезопасный подразумевает const
и поэтому оба не эквивалентны. const
все еще подразумевает «логически неизменяемость», просто область «логически неизменности» расширилась, чтобы требовать безопасности потоков.
Если мы возьмем const
и потокобезопасен, чтобы быть эквивалентным, мы теряем приятную особенность const
что позволяет нам легко рассуждать о коде, видя, где значения могут быть изменены:
//`a` is `const` because `const` and thread-safe are equivalent.
//Does this function modify a?
void foo(std::atomic<int> const& a);
Кроме того, соответствующий раздел [res.on.data.races]
говорит об «модификациях», которые могут быть разумно истолкованы в более общем смысле «изменений, наблюдаемых извне», а не просто «изменений, небезопасных для потока».
Проблема с 3
просто, что это может быть правдой, только если 2
это правда, и 2
является критически ошибочным.
Таким образом, чтобы применить это к вашему вопросу — нет, вы не должны делать каждый внутренне синхронизированный объект mutable
,
Вы должны зарезервировать mutable
для переменных-членов, которые не влияют на внешне видимое состояние объекта. С другой стороны (и это ключевой момент, который Херб Саттер делает в своем выступлении), если у вас есть член, который является изменчивый по какой-то причине, этот член должен быть внутренне синхронизированным, в противном случае вы рискуете сделать const
не подразумевает поточно-ориентированный, и это приведет к неопределенному поведению со стандартной библиотекой.
Давайте поговорим об изменении в const
,
void somefunc(Foo&);
void somefunc(const Foo&);
В C ++ 03 и ранее const
версия, по сравнению сconst
один, предоставляет дополнительные гарантии для абонентов. Он обещает не изменять свой аргумент, где под модификацией мы подразумеваем вызов Foo
неконстантные функции-члены (включая присваивание и т. д.) или передача их функциям, ожидающимconst
аргумент, или делать то же самое с его открытыми неизменяемыми элементами данных. somefunc
ограничивается const
операции на Foo
, И дополнительная гарантия полностью односторонняя. Ни звонящий, ни Foo
провайдер не должен делать ничего особенного, чтобы позвонить const
версия. Любой, кто может позвонитьconst
версия может назвать const
версия тоже.
В C ++ 11 это меняется. const
Версия по-прежнему предоставляет ту же гарантию звонящему, но теперь она идет с ценой. Поставщик Foo
должен убедиться, что все const
операции потокобезопасны. Или, по крайней мере, так должно быть, когда somefunc
является стандартной библиотечной функцией Зачем? Потому что стандартная библиотека может распараллелить его операции, и это будут вызов const
операции над чем угодно и без всякой дополнительной синхронизации. Таким образом, вы, пользователь, должны убедиться, что дополнительная синхронизация не нужна. Конечно, это не проблема в большинстве случаев, так как большинство классов не имеют изменяемых членов, и большинство const
операции не затрагивают глобальные данные.
И что mutable
значит сейчас? Это так же, как и раньше! А именно, эти данные неконстантны, но это детали реализации, я обещаю, что они не влияют на наблюдаемое поведение. Это означает, что нет, вам не нужно отмечать все в поле зрения mutable
так же, как вы не делали этого в C ++ 98. Поэтому, когда вы должны отметить элемент данных mutable
? Так же, как в C ++ 98, когда вам нужно вызвать егоconst
операции от const
метод, и вы можете гарантировать, что он ничего не сломает. Повторить:
mutable
,Первое условие накладывается, как в C ++ 98, потому что другой код, включая стандартную библиотеку, может вызывать ваш const
методы, и никто не должен наблюдать какие-либо изменения в результате таких вызовов. Второе условие существует, и это то, что является новым в C ++ 11, потому что такие вызовы могут выполняться асинхронно.
Принятый ответ охватывает вопрос, но стоит упомянуть, что Саттер с тех пор изменил слайд, который неверно предположил, что const == mutable == потоко-безопасен. Сообщение в блоге, которое приводит к этому изменению слайда, может быть найдено здесь:
Что Саттер ошибся в Const в C ++ 11
TL: DR Const и Mutable подразумевают поточно-ориентированный, но имеют разные значения в отношении того, что можно и нельзя изменить в вашей программе.