简体   繁体   English

在 C++11 中总是将 std::mutex 声明为可变的?

[英]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_evenis_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.同样的, isEvenisPrime :本次测试的结果应该不会改变,所以强制执行。

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:如果我理解正确,他的论点如下:

  1. [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对与标准库一起使用的类型提出了要求——非常量成员函数必须是线程安全的。

  2. Therefore const is equivalent to thread-safe.因此const等效于线程安全。

  3. 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

In C++11, as in C++03, `const` means "logically immutable" and `mutable` means "can change, but the change will not be externally observable".在 C++11 中,就像在 C++03 中一样,`const` 的意思是“逻辑上不可变的”,而 `mutable` 的意思是“可以改变,但这种变化不会被外部观察到”。 The only difference is that in C++11, "logically immutable" has been expanded to include "thread-safe".唯一的区别是在 C++11 中,“逻辑不可变”已扩展为包括“线程安全”。

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:重申:

  • if your data member's physical state does not affect the observable state of the object如果您的数据成员的物理状态不影响对象的可观察状态
  • and it is thread-safe (internally synchronized)是线程安全的(内部同步)
  • then you can (if you need to!) go ahead and declare it 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM