Всегда объявлять std :: mutex как изменчивый в C ++ 11?

После просмотра выступления Херба Саттера Вы не знаете, постоянный и изменчивый, Интересно, должен ли я всегда определять мутекс как изменяемый? Если да, я думаю, что то же самое верно для любого синхронизированного контейнера (например, tbb::concurrent_queue)?

Немного предыстории: в своем выступлении он заявил, что const == mutable == thread-safe, и std::mutex по определению является потокобезопасным.

Есть также связанный вопрос о разговоре, Означает ли const поточно-ориентированный в C ++ 11.

Редактировать:

Вот, Я нашел связанный вопрос (возможно, дубликат). Это было задано до C ++ 11, хотя. Может быть, это имеет значение.

35

Решение

Нет. Однако большую часть времени они будут.

Хотя полезно думать о 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,

35

Другие решения

Я просто смотрел разговор и не совсем согласен с тем, что говорит Херб Саттер.

Если я правильно понимаю, его аргумент таков:

  1. [res.on.data.races]/3 накладывает требование на типы, которые используются со стандартной библиотекой — неконстантные функции-члены должны быть поточно-ориентированными.

  2. Следовательно const эквивалентно потокобезопасному.

  3. И если 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,

В C ++ 11, как и в C ++ 03, «const» означает «логически неизменный», а «mutable» означает «может измениться, но изменение не будет наблюдаться извне». Единственное отличие состоит в том, что в C ++ 11 «логически неизменный» был расширен, чтобы включить «потокобезопасный».

Вы должны зарезервировать mutable для переменных-членов, которые не влияют на внешне видимое состояние объекта. С другой стороны (и это ключевой момент, который Херб Саттер делает в своем выступлении), если у вас есть член, который является изменчивый по какой-то причине, этот член должен быть внутренне синхронизированным, в противном случае вы рискуете сделать const не подразумевает поточно-ориентированный, и это приведет к неопределенному поведению со стандартной библиотекой.

10

Давайте поговорим об изменении в 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, потому что такие вызовы могут выполняться асинхронно.

6

Принятый ответ охватывает вопрос, но стоит упомянуть, что Саттер с тех пор изменил слайд, который неверно предположил, что const == mutable == потоко-безопасен. Сообщение в блоге, которое приводит к этому изменению слайда, может быть найдено здесь:

Что Саттер ошибся в Const в C ++ 11

TL: DR Const и Mutable подразумевают поточно-ориентированный, но имеют разные значения в отношении того, что можно и нельзя изменить в вашей программе.

3
По вопросам рекламы [email protected]