you should (strictly) prefer std::scoped_lock over std::lock_guard.
your scope locked takes an std::mutex, not a map (ie in its constructor)
the lambda passed to foo is called a completion handler; one way to thread a bunch of (related) handlers without needing explicit locks is to use so-called strands. As long as all the operations which have to be performed serially are coroutines, spawned within strand in question, you can actually have a thread pool of executors running, and asio will take care of all the locking complexity for you.
you’re using p in the block as a whole, and within the completion handler, so be aware that the poutside has to be well-defined, and that the interior one (in the lambda) shadows the outer one. (I’m a fan of shadowing, btw, the company I used to have lint settings which yelled when shadowing happens, but for me it’s one of the features I want, because it leads to more concise, uniform, clear names – and that in turn is because shadowing allows them to be reused, but in the specific context… anyyyyway)
modern C++ tends to favour async style code. Instead of passing a completion handler to foo, you make foo an awaitable functor which co_yields an index (p, above), one which we can co_await as in:
// note: the below has to run in a coroutine
...
my_map[q].insert(10); // renamed outer p to q to avoid collision now that async-style leaves p in the same scope as outer code!constauto& p = co_awaitfoo(bar1);
// use p
If you want to do 5 on existing code, follow the guidelines here to wrap legacy callback code into something that works with C++20 awaitables.
If I may be so bold, you’re describing something intrinsically async, so you may want to consider using boost::asio, then you get access to all this.
and nowadays, all this is dead easy to install using conan
A few points:
std::scoped_lock
overstd::lock_guard
.std::mutex
, not a map (ie in its constructor)foo
is called a completion handler; one way to thread a bunch of (related) handlers without needing explicit locks is to use so-called strands. As long as all the operations which have to be performed serially are coroutines, spawned within strand in question, you can actually have a thread pool of executors running, and asio will take care of all the locking complexity for you.p
in the block as a whole, and within the completion handler, so be aware that thep
outside has to be well-defined, and that the interior one (in the lambda) shadows the outer one. (I’m a fan of shadowing, btw, the company I used to have lint settings which yelled when shadowing happens, but for me it’s one of the features I want, because it leads to more concise, uniform, clear names – and that in turn is because shadowing allows them to be reused, but in the specific context… anyyyyway)foo
, you makefoo
an awaitable functor whichco_yields
an index (p
, above), one which we canco_await
as in:// note: the below has to run in a coroutine ... my_map[q].insert(10); // renamed outer p to q to avoid collision now that async-style leaves p in the same scope as outer code! const auto& p = co_await foo(bar1); // use p
boost::asio
, then you get access to all this.