[英]Always declare std::mutex as mutable in C++11?
After watching Herb Sutter's talk You Don't Know const and mutable , I wonder whether I should always define a mutex as mutable?看了 Herb Sutter 的演讲你不知道 const 和 mutable ,我想知道我是否应该总是将互斥体定义为可变的? If yes, I guess the same holds for any synchronized container (eg,
tbb::concurrent_queue
)?如果是,我猜对于任何同步容器(例如,
tbb::concurrent_queue
)也是如此?
Some background: In his talk, he stated that const == mutable == thread-safe, and std::mutex
is per definition thread-safe.一些背景:在他的演讲中,他指出 const == mutable == 线程安全,而
std::mutex
是每个定义线程安全的。
There is also related question about the talk, Does const mean thread-safe in C++11 .也有关于谈话的相关问题, 在 C++11 中 const 是否意味着线程安全。
Edit:编辑:
Here , I found a related question (possibly a duplicate). 在这里,我发现了一个相关的问题(可能是重复的)。 It was asked before C++11, though.
不过,在 C++11 之前就有人问过了。 Maybe that makes a difference.
也许这会有所作为。
No. However, most of the time they will be.不。但是,大多数时候他们会。
While it's helpful to think of const
as "thread-safe" and mutable
as "(already) thread-safe", const
is still fundamentally tied to the notion of promising "I won't change this value".虽然将
const
视为“线程安全”并将mutable
视为“(已经)线程安全”是有帮助的,但const
仍然从根本上与承诺“我不会改变这个值”的概念联系在一起。 It always will be.永远都是。
I have a long-ish train of thought so bear with me.我有一个很长的思路,所以请耐心等待。
In my own programming, I put const
everywhere.在我自己的编程中,我把
const
放在任何地方。 If I have a value, it's a bad thing to change it unless I say I want to.如果我有一个价值,除非我说我想改变它,否则改变它是一件坏事。 If you try to purposefully modify a const-object, you get a compile-time error (easy to fix and no shippable result!).
如果你试图有目的地修改一个 const 对象,你会得到一个编译时错误(容易修复并且没有可交付的结果!)。 If you accidentally modify a non-const object, you get a runtime programming error, a bug in a compiled application, and a headache.
如果不小心修改了非常量对象,则会出现运行时编程错误、已编译应用程序中的错误以及头痛。 So it's better to err on the former side and keep things
const
.所以最好在前一方面犯错并保持
const
。
For example:例如:
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;
}
}
Why are the parameter types for is_even
and is_prime
marked const
?为什么
is_even
和is_prime
的参数类型标记为const
? Because from an implementation point of view, changing the number I'm testing would be an error!因为从实现的角度来看,更改我正在测试的数字将是一个错误! Why
const auto& x
?为什么
const auto& x
? Because I don't intend on changing that value, and I want the compiler to yell at me if I do.因为我不打算更改该值,并且我希望编译器在我更改时对我大喊大叫。 Same with
isEven
and isPrime
: the result of this test should not change, so enforce it.同样的,
isEven
和isPrime
:本次测试的结果应该不会改变,所以强制执行。
Of course const
member functions are merely a way to give this
a type of the form const T*
.当然,
const
成员函数只是给this
一个const T*
形式的类型的方法。 It says "it would be an error in implementation if I were to change some of my members".它说“如果我要改变我的一些成员,这将是一个实施错误”。
mutable
says "except me". mutable
说“除了我”。 This is where the "old" notion of "logically const" comes from.这就是“逻辑常量”的“旧”概念的来源。 Consider the common use-case he gave: a mutex member.
考虑他给出的常见用例:互斥锁成员。 You need to lock this mutex to ensure your program is correct, so you need to modify it.
你需要锁定这个互斥锁以确保你的程序是正确的,所以你需要修改它。 You don't want the function to be non-const, though, because it would be an error to modify any other member.
但是,您不希望该函数是非常量的,因为修改任何其他成员都是错误的。 So you make it
const
and mark the mutex as mutable
.因此,您将其设为
const
并将互斥锁标记为mutable
。
None of this has to do with thread-safety.这些都与线程安全无关。
I think it's one step too far to say the new definitions replace the old ideas given above;我认为说新定义取代了上面给出的旧想法有点过头了。 they merely complement it from another view, that of thread-safety.
他们只是从另一个角度补充它,即线程安全。
Now the point of view Herb gives that if you have const
functions, they need to be thread-safe to be safely usable by the standard library.现在 Herb 的观点是,如果你有
const
函数,它们需要是线程安全的,才能被标准库安全使用。 As a corollary of this, the only members you should really mark as mutable
are those that are already thread-safe, because they are modifiable from a const
function:因此,您应该真正标记为
mutable
的唯一成员是那些已经是线程安全的成员,因为它们可以从const
函数进行修改:
struct foo
{
void act() const
{
mNotThreadSafe = "oh crap! const meant I would be thread-safe!";
}
mutable std::string mNotThreadSafe;
};
Okay, so we know that thread-safe things can be marked as mutable
, you ask: should they be?好的,所以我们知道线程安全的东西可以被标记为
mutable
,你问:他们应该吗?
I think we have to consider both view simultaneously.我认为我们必须同时考虑这两种观点。 From Herb's new point of view, yes.
从 Herb 的新观点来看,是的。 They are thread safe so do not need to be bound by the const-ness of the function.
它们是线程安全的,因此不需要受限于函数的常量性。 But just because they can safely be excused from the constraints of
const
doesn't mean they have to be.但是仅仅因为它们可以安全地摆脱
const
的约束并不意味着它们必须如此。 I still need to consider: would it be an error in implementation if I did modify that member?我还需要考虑:如果我确实修改了那个成员,它会不会在实现中出错? If so, it needs to not be
mutable
!如果是这样,它不需要是
mutable
!
There's a granularity issue here: some functions may need to modify the would-be mutable
member while others don't.这里有一个粒度问题:一些函数可能需要修改潜在的
mutable
成员,而其他函数则不需要。 This is like wanting only some functions to have friend-like access, but we can only friend the entire class.这就像只希望某些函数具有类似友元的访问权限,但我们只能将整个类添加为友元。 (It's a language design issue.)
(这是一个语言设计问题。)
In this case, you should err on the side of mutable
.在这种情况下,您应该站在
mutable
一边犯错。
Herb spoke just slightly too loosely when he gave a const_cast
example an declared it safe.当 Herb 给出一个
const_cast
示例并声明它是安全的时,他说得有点过于松散了。 Consider:考虑:
struct foo
{
void act() const
{
const_cast<unsigned&>(counter)++;
}
unsigned counter;
};
This is safe under most circumstances, except when the foo
object itself is const
:这在大多数情况下是安全的,除非
foo
对象本身是const
:
foo x;
x.act(); // okay
const foo y;
y.act(); // UB!
This is covered elsewhere on SO, but const foo
, implies the counter
member is also const
, and modifying a const
object is undefined behavior.这在 SO 的其他地方有介绍,但是
const foo
意味着counter
成员也是const
,并且修改const
对象是未定义的行为。
This is why you should err on the side of mutable
: const_cast
does not quite give you the same guarantees.这就是为什么你应该在
mutable
方面犯错: const_cast
并没有给你同样的保证。 Had counter
been marked mutable
, it wouldn't have been a const
object.如果
counter
被标记为mutable
,它就不会是一个const
对象。
Okay, so if we need it mutable
in one spot we need it everywhere, and we just need to be careful in the cases where we don't.好的,所以如果我们需要它在一个地方
mutable
,那么我们在任何地方都需要它,我们只需要在不需要的情况下小心。 Surely this means all thread-safe members should be marked mutable
then?这当然意味着所有线程安全成员都应该被标记为
mutable
吗?
Well no, because not all thread-safe members are there for internal synchronization.不,因为并非所有线程安全成员都用于内部同步。 The most trivial example is some sort of wrapper class (not always best practice but they exist):
最简单的例子是某种包装类(并不总是最佳实践,但它们存在):
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;
};
Here we are wrapping threadsafe_container
and providing another member function we want (would be better as a free function in practice).这里我们包装了
threadsafe_container
并提供了另一个我们想要的成员函数(在实践中作为自由函数会更好)。 No need for mutable
here, the correctness from the old point of view utterly trumps: in one function I'm modifying the container and that's okay because I didn't say I wouldn't (omitting const
), and in the other I'm not modifying the container and ensure I'm keeping that promise (omitting mutable
).这里不需要
mutable
,从旧的观点来看,正确性完全胜过:在一个函数中,我正在修改容器,这没关系,因为我没有说我不会(省略const
),而在另一个const
中我'我没有修改容器并确保我遵守承诺(省略mutable
)。
I think Herb is arguing the most cases where we'd use mutable
we're also using some sort of internal (thread-safe) synchronization object, and I agree.我认为 Herb 是在争论我们使用
mutable
的大多数情况,我们也在使用某种内部(线程安全)同步对象,我同意。 Ergo his point of view works most of the time.因此,他的观点大部分时间都有效。 But there exist cases where I simply happen to have a thread-safe object and merely treat it as yet another member;
但是存在这样的情况,我只是碰巧有一个线程安全对象,而只是将它视为另一个成员; in this case we fall back on the old and fundamental use of
const
.在这种情况下,我们回到
const
的旧和基本用法。
I just watched the talk, and I do not entirely agree with what Herb Sutter is saying.我刚刚看了演讲,我并不完全同意赫伯·萨特的说法。
If I understand correctly, his argument is as follows:如果我理解正确,他的论点如下:
[res.on.data.races]/3
imposes a requirement on types that are used with the standard library -- non-const member functions must be thread-safe. [res.on.data.races]/3
对与标准库一起使用的类型提出了要求——非常量成员函数必须是线程安全的。
Therefore const
is equivalent to thread-safe.因此
const
等效于线程安全。
And if const
is equivalent to thread-safe, the mutable
must be equivalent to "trust me, even the non-const members of this variable are thread-safe".如果
const
等价于线程安全,那么mutable
必须等价于“相信我,即使这个变量的非常量成员也是线程安全的”。
In my opinion, all three parts of this argument are flawed (and the second part is critically flawed).在我看来,这个论点的所有三个部分都有缺陷(第二部分存在严重缺陷)。
The problem with 1
is that [res.on.data.races]
gives requirements for types in the standard library, not types to be used with the standard library. 1
的问题在于[res.on.data.races]
给出了标准库中类型的要求,而不是标准库中使用的类型。 That said, I think it is reasonable (but not entirely clear-cut) to interpret [res.on.data.races]
as also giving requirements for types to be used with the standard library, because it would be practically impossible for a library implementation to uphold the requirement to not modify objects through const
references if const
member functions were able to modify objects.也就是说,我认为将
[res.on.data.races]
解释为也给出了与标准库一起使用的类型的要求是合理的(但并不完全明确),因为对于库来说这实际上是不可能的如果const
成员函数能够修改对象,则实现不通过const
引用修改对象的要求。
The critical problem with 2
is that while it is true (if we accept 1
) that const
must imply thread-safe, it is not true that thread-safe implies const
, and so the two are not equivalent.与关键问题
2
是,虽然这是事实(如果我们接受1
)该const
必须意味着线程安全的,这是不正确的线程安全意味着const
,所以这两者是不等价的。 const
still implies "logically immutable", it is just that the scope for "logically immutability" has expanded to require thread-safety. const
仍然意味着“逻辑上不可变”,只是“逻辑上不可变”的范围已经扩展到需要线程安全。
If we take const
and thread-safe to be equivalent, we lose the nice feature of const
which is that it allows us to easily reason about code by seeing where values can be modified:如果我们认为
const
和线程安全是等价的,我们就会失去const
的优点,那就是它允许我们通过查看可以修改值的位置来轻松推理代码:
//`a` is `const` because `const` and thread-safe are equivalent.
//Does this function modify a?
void foo(std::atomic<int> const& a);
Furthermore, the relevant section of [res.on.data.races]
talks about "modifies", which can be reasonably interpreted in the more general sense of "changes in an externally observable way", rather than just "changes in a thread-unsafe way".此外,
[res.on.data.races]
的相关部分谈到了“修改”,可以在更一般的意义上合理地解释“以外部可观察的方式进行更改”,而不仅仅是“线程中的更改”-不安全的方式”。
The problem with 3
is simply that it can only be true if 2
is true, and 2
is critically flawed. 3
的问题很简单,只有当2
为真时它才能为真,而2
存在严重缺陷。
So to apply this to your question -- no, you should not make every internally synchronised object mutable
.因此,要将其应用于您的问题 - 不,您不应该使每个内部同步对象
mutable
。
You should reserve mutable
for member variables that do not affect the externally visible state of the object.您应该为不影响对象外部可见状态的成员变量保留
mutable
的。 On the other hand (and this is the key point that Herb Sutter makes in his talk), if you have a member that is mutable for some reason, that member must be internally synchronised, otherwise you risk making const
not imply thread-safe, and this would cause undefined behaviour with the standard library.另一方面(这是 Herb Sutter 在他的演讲中提出的关键点),如果您有一个由于某种原因可变的成员,则该成员必须在内部同步,否则您可能会使
const
不意味着线程安全,这会导致标准库出现未定义的行为。
Let's talk about the change in const
.让我们谈谈
const
的变化。
void somefunc(Foo&);
void somefunc(const Foo&);
In C++03 and before, the const
version, compared to the non- const
one, provides additional guarantees to the callers.在 C++03 及
const
版本中,与非const
版本相比, const
版本为调用者提供了额外的保证。 It promises not to modify its argument, where by modification we mean calling Foo
's non-const member functions (including assignment etc), or passing it to functions that expect a non- const
argument, or doing same to its exposed non-mutable data members.它承诺不修改其参数,在这里通过修改我们的意思是叫
Foo
的非const成员函数(包括分配等),或者将它传递给那些期望非功能const
参数,或做同样其裸露的非易变数据成员。 somefunc
restricts itself to const
operations on Foo
. somefunc
将自身限制为Foo
上的const
操作。 And the additional guarantee is totally one-sided.而额外的保证完全是片面的。 Neither the caller nor the
Foo
provider do not have to do anything special in order to call the const
version.调用者和
Foo
提供者都不需要做任何特殊的事情来调用const
版本。 Anyone who is able to call the non- const
version can call the const
version too.任何能够调用非
const
版本的人也可以调用const
版本。
In C++11 this changes.在 C++11 中,这发生了变化。 The
const
version still provides the same guarantee to the caller, but now it comes with a price. const
版本仍然为调用者提供相同的保证,但现在它带来了代价。 The provider of Foo
must make sure that all const
operations are thread safe . Foo
的提供者必须确保所有const
操作都是线程安全的。 Or it least it must do so when somefunc
is a standard library function.或者至少当
somefunc
是标准库函数时它必须这样做。 Why?为什么? Because the standard library may parallelize its operations, and it will call
const
operations on anything and everything without any additional synchronization.因为标准库可以并行化它的操作,它会在没有任何额外同步的情况下对任何东西调用
const
操作。 So you, the user, must make sure this additional synchronization is not needed.因此,您,用户,必须确保不需要这种额外的同步。 Of course this is not a problem in most cases, as most classes have no mutable members and most
const
operations don't touch global data.当然,在大多数情况下这不是问题,因为大多数类没有可变成员,并且大多数
const
操作不涉及全局数据。
So what mutable
means now?那么现在
mutable
是什么意思呢? It's the same as before!和以前一样! Namely, this data is non-const, but it is an implementation detail, I promise it does not affect the observable behavior.
也就是说,这个数据是非常量的,但它是一个实现细节,我保证它不会影响可观察的行为。 This means that no, you don't have to mark everything in sight
mutable
, just as you didn't do it in C++98.这意味着不,您不必像在 C++98 中那样标记所有可见的东西
mutable
一样。 So when you should mark a data member mutable
?那么什么时候应该将数据成员标记为
mutable
呢? Just like in C++98, when you need to call its non- const
operations from a const
method, and you can guarantee it won't break anything.就像在 C++98 中一样,当你需要从一个
const
方法调用它的非const
操作时,你可以保证它不会破坏任何东西。 To reiterate:重申:
mutable
.mutable
。 The first condition is imposed, like in C++98, because other code, including the standard library, may call your const
methods and nobody should observe any changes resulting from such calls.第一个条件是强加的,就像在 C++98 中一样,因为其他代码,包括标准库,可能会调用您的
const
方法,并且没有人应该观察到此类调用导致的任何更改。 The second condition is there, and this is what's new in C++11, because such calls can be made asynchronously.第二个条件存在,这就是 C++11 中的新功能,因为此类调用可以异步进行。
The accepted answer covers the question, but it's worth mentioning that Sutter has since changed the slide that incorrectly suggested that const == mutable == thread-safe.接受的答案涵盖了这个问题,但值得一提的是,萨特后来更改了错误地建议 const == mutable == thread-safe 的幻灯片。 The blog post that lead to that slide change can be found here:
可以在此处找到导致该幻灯片更改的博客文章:
What Sutter got wrong about Const in C++11 Sutter 对 C++11 中的 Const 有什么误解
TL:DR Const and Mutable both imply Thread-safe, but have different meanings in regards to what can and can't be changed in your program. TL:DR Const 和 Mutable 都意味着线程安全,但在程序中可以更改和不可以更改的内容方面具有不同的含义。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.