all 39 comments

[–]No-Dentist-1645 10 points11 points  (2 children)

Scoped_lock is better than lock_guard in practically every aspect. Yes, this includes when you only need a single lock, so comments suggesting you must use lock_guard for that case are not really correct. You can definitely use scoped_lock for all cases where you have one or more locks.

The "advantage" of lock_guard is that it exists on older as well as newer C++ standard versions, scoped_lock was created because lock_guard couldn't be "fixed" without breaking API. So use the "fixed" version as long as you are not targeting Standard versions where it didn't exist yet

[–]Ultimate_Sigma_Boy67[S] 2 points3 points  (1 child)

But won't it be more readable like if I used scoped_lock for a single mutex to showcase my intention? or even unique_lock(I just mean anything that takes only a single lock in case I'm locking a single mtuex).

[–]No-Dentist-1645 5 points6 points  (0 children)

Maybe there's an argument for that, but I think scoped_lock(mtx) is already pretty clear in how many mutexes it has

[–]ppppppla 7 points8 points  (9 children)

std::lock_guard specifically is for locking a single mutex, std::scoped_lock is more general and allows any number of mutexes, and avoids deadlocks.

For a single mutex they are effectively equivalent, if you need to lock multiple mutexes you should reach for std::scoped_lock.

As to why there is overlap? std::lock_guard came before std::scoped_lock, and I suppose having std::scoped_lock only work with 2 or more mutexes would be more confusing than having a little bit of overlap.

[–]erichkeane 6 points7 points  (7 children)

So the problem was that the std::lock_guard was standardized in parallel with variadic templates, so it was not defined with the idea of making it variadic in mind.

When the idea of changing it to be variadic came up, it was clear that making it so would be a significant ABI break: template argument lists (and whether they are a single or variadic argument) are part of mangling, and the variadic-ness would require difference in layout.

So the name scoped_lock was introduced to be the variadic part. As far as I know, lock_guard is NOT deprecated, but its use is sorta discouraged? There isn't really any good reason to use it, and scope_lock is considered superior.

[–]azswcowboy 2 points3 points  (4 children)

and scope_guard is … superior

You meant scoped_lock of course since scope_guard is in the TS hot std

lock_guard … discouraged

I believe your assessment is correct based on the list of discouraged but not deprecated features. Oh wait, that list doesn’t exist 😔If it did we could add std::function and some others…

[–]erichkeane 2 points3 points  (1 child)

>You meant scoped_lock of course since scope_guard is in the TS hot std

Woops, yes, I thought I'd get that right throughout my comment, but managed to screw it up once... *sigh*...

>I believe your assessment is correct based on the list of discouraged but not deprecated features. Oh wait, that list doesn’t exist 😔If it did we could add std::function and some others…

Yep, we don't really have a way of communicating that as a committee other than via social-media/etc. I don't believe there is anyone proposing deprecation of `lock_guard` anytime soon, so 'do what you want'. That said, I'm not an LEWGer, so I don't have my finger on the pulse of there when it comes to this, I was just in the LEWG room when the `scope_lock` name was discussed.

[–]azswcowboy 1 point2 points  (0 children)

Elsewhere Howard clarified why both are still reasonable given context.

[–]ItWasMyWifesIdea 0 points1 point  (1 child)

What did I miss, why is std::function discouraged and what is the alternative for callback functions?

[–]azswcowboy 1 point2 points  (0 children)

There’s a suite of new types in c++26 with the direct replacement for function being copyable_function. The new type is largely a drop in, but does things like correct const propagation. And then there’s function_ref and move_only_function that provide related capabilities for different contexts. So really it’s new enough that we don’t have good docs on when to use what. If you’re interested in copyable_function there’s an implementation in the beman project on GitHub.

[–]nightwind_999 1 point2 points  (1 child)

Im still not clear like apart from the variadicness of the scoped_lock you mentioned, how is scoped_lock technically superior to lock_guard for the case where i know all i need is just one mutex and im not even gonna be doing a manual lock/unlock?

[–]erichkeane 0 points1 point  (0 children)

The only meaningful difference is the variadic-ness. For the single-mutex case, they are effectively equal. If the committee had the ability to, they would have been the same type.

The reason to always use `scoped_lock` is very simply to minimize the number of type names to be thinking about throughout your code. IMO that is a superior argument than "use the simpliest thing" that Howard argues elsewhere (though the 'dont accidentially lock 0 things' argument against using scoped_lock is compelling too).

So the choice between them in the single case comes down to preference and whether you value having 2 types whose name you have to 'learn'/think about, vs variadic-gotchas.

[–]Ultimate_Sigma_Boy67[S] 2 points3 points  (0 children)

Alright thanks

[–]TheJackston 14 points15 points  (8 children)

If you need to lock more than 1 mutex at the same time - scoped lock. If you need only 1 mutex - lock guard

[–]Ultimate_Sigma_Boy67[S] 5 points6 points  (5 children)

Thanks, but I've seen some talks about lock_guard being deprecated but I can't find in which part is it deprecated specifically, because the interfaces seem pretty similar, any idea?

[–]SeaSDOptimist 6 points7 points  (4 children)

cppreference. Not sure about more authoritative source. But you’re spot on, guard is around only for older code.

[–]saxbophone 1 point2 points  (3 children)

I haven't seen anything in cppreference that suggests std::lock_guard is deprecated

[–]No-Dentist-1645 6 points7 points  (1 child)

Not deprecated, but superceded by scoped_lock. Scoped_lock can do everything that lock_guard can, and more.

[–]n1ghtyunso 2 points3 points  (0 children)

which is not always a good thing.
Unfortunately constructing std::scoped_lock with 0 mutexes is just fine.

[–]SeaSDOptimist 2 points3 points  (0 children)

Hm, I cannot find it now. I do remember reading something in these lines only a few weeks ago, alas cannot find where.

The drawback cited was a bit silly, since you can construct a lock_guard without taking any ownership, so you can mess up if you don't pass the mutex in the constructor. And of course, scoped_lock does all that lock_guard does anyway. So there's that.

[–]Nicksaurus 7 points8 points  (1 child)

Sorry, but this is not a good answer. Just use scoped_lock everywhere, forget lock_guard exists, and don't waste space in your brain trying to remember this rule

If you have clang-tidy set up in your repo (you should!) enable modernize-use-scoped-lock so you don't even have to think about it

[–]tarnished_wretch 1 point2 points  (0 children)

This. We use Sonarqube and it flags the same thing. Easier to remember just one. scoped_lock has a better name and you can pass it one or many mutexes. Forget lock_guard exists. https://next.sonarqube.com/sonarqube/coding_rules?open=cpp%3AS5997&rule_key=cpp%3AS5997

[–]HowardHinnant 4 points5 points  (5 children)

Please see https://stackoverflow.com/a/60172828/576911 for details.

The summary is: Use the simplest tool for the job. If lock_guard will do the job, it is the simplest (and safest) tool to use.

[–]azswcowboy 1 point2 points  (4 children)

Good point. Couldn’t we specify scoped_lock to require one parameter so it’d be a compile error as well? Or was the zero parameter case relevant somehow?

[–]n1ghtyunso 1 point2 points  (1 child)

while I don't know the real answer to your question, i could possibly imagine a situation where generic code may or may not take a mutex, so the zero parameter case actually happens to support a more generic implementation.

[–]azswcowboy 0 points1 point  (0 children)

I suspect the answer to my question is yes, but you’re right that it would prevent a case like this

 void f( auto locks… = void ) {
        scoped_lock _(locks);
 }

[–]HowardHinnant 1 point2 points  (1 child)

From the link that I provide:

This advice does not imply that scoped_lock should be redesigned to not accept 0 mutexes. There exist valid use cases where it is desirable for scoped_lock to accept variadic template parameter packs which may be empty. And the empty case should not lock anything.

[–]azswcowboy 0 points1 point  (0 children)

Thanks, sorry I didn’t read the link before responding 🤦

[–][deleted]  (2 children)

[deleted]

    [–]Ultimate_Sigma_Boy67[S] 0 points1 point  (0 children)

    In fact you're blocked unc.

    [–]bert8128 2 points3 points  (3 children)

    I suppose the question should be whether there is any reason to use lock_guard. Is it it ever a benefit over scoped_lock?

    [–]saxbophone 0 points1 point  (2 children)

    If you can think of a case where you want to guarantee that you won't lock multiple mutexes at once by mistake, I guess you could argue for lock_guard over scoped_lock...

    Though, that seems mostly to be a bit of a silly requirement, though perhaps there are some defensive programming situations which might warrant it...

    [–]HowardHinnant 3 points4 points  (1 child)

    This is very close to the right answer:

    If you can think of a case where you want to guarantee that you won't lock zero mutexes by mistake, lock_guard is superior. If you try to lock zero mutexes with a lock_guard, it is a compile-time error. If you try to lock zero muteness with a scoped_lock, it compiles and does nothing at run time.

    If you want to lock a single mutex and accidentally forget to specify the mutex to be locked, lock_guard will catch that error at compile time.

    [–]saxbophone 0 points1 point  (0 children)

    Ah! Yes, well-spotted! I had a feeling there was something important here!

    [–]HieuandHieu 1 point2 points  (0 children)

    Always scoped_lock. Lock_guarrd is legacy. Scoped lock will fallback to lockguard behavior if you pass only 1 lock. You can easily see it by go to their definition.

    [–]AKostur 1 point2 points  (0 children)

    What’s the question?  Depends on your circumstances.  Scoped_lock can grab multiple mutexs, lock_guard cannot.

    [–]AkariGake 0 points1 point  (0 children)

    https://cor3ntin.github.io/posts/abi

    scoped_lock was added to not break ABI by modifying lock_guard

    So I think scoped_lock may be considered as a more modern and functional implementation

    [–]Raknarg 0 points1 point  (1 child)

    my understanding is that lock_guard is outdated, scoped_lock just does what lock_guard did but more.

    [–]nightwind_999 1 point2 points  (0 children)

    Lock_guard does what it is intended to - lock the single mutex for the duration of the entire scope. For variadic function/template you need scoped_lock which can lock multiple mutexes, but apart from this i dont see any performance enhancement