Widget w;
using namespace std::placeholders;
auto compressRateB = std::bind(compress, w, _1);
Now, when we pass w
to std::bind
, it has to be stored for the later call to compress
. It's stored inside the object compressRateB
, but how is it stored — by value or by reference? It makes a difference, because if w
is modified between the call to std::bind
and a call to compressRateB
, storing w
by reference will reflect the changes, while storing it by value won't.
The answer is that it's stored by value, [14] std::bind always copies its arguments, but callers can achieve the effect of having an argument stored by reference by applying std::ref to it. The result of auto compressRateB = std::bind(compress, std::ref( w ) , _1); is that compressRateB acts as if it holds a reference to w , rather than a copy.
but the only way to know that is to memorize how std::bind
works; there's no sign of it in the call to std::bind
. Contrast that with a lambda approach, where whether w
is captured by value or by reference is explicit:
auto compressRateL = // w is captured by
[ w](CompLevel lev) // value; lev is
{ return compress(w, lev); }; // passed by value
Equally explicit is how parameters are passed to the lambda. Here, it's clear that the parameter lev
is passed by value. Hence:
compressRate L(CompLevel::High); // arg is passed
// by value
But in the call to the object resulting from std::bind
, how is the argument passed?
compressRate B(CompLevel::High); // how is arg
// passed?
Again, the only way to know is to memorize how std::bind
works. (The answer is that all arguments passed to bind objects are passed by reference, because the function call operator for such objects uses perfect forwarding.)
Compared to lambdas, then, code using std::bind
is less readable, less expressive, and possibly less efficient. In C++14, there are no reasonable use cases for std::bind
. In C++11, however, std::bind can be justified in two constrained situations:
• Move capture.C++11 lambdas don't offer move capture, but it can be emulated through a combination of a lambda and std::bind
. For details, consult Item 32, which also explains that in C++14, lambdas' support for init capture eliminates the need for the emulation.
• Polymorphic function objects.Because the function call operator on a bind object uses perfect forwarding, it can accept arguments of any type (modulo the restrictions on perfect forwarding described in Item 30). This can be useful when you want to bind an object with a templatized function call operator. For example, given this class,
class PolyWidget {
public:
template
void operator()(const T& param);
…
};
std::bind
can bind a PolyWidget
as follows:
PolyWidget pw;
auto boundPW = std::bind(pw, _1);
boundPW
can then be called with different types of arguments:
boundPW(1930); // pass int to
// PolyWidget::operator()
boundPW(nullptr); // pass nullptr to
// PolyWidget::operator()
boundPW("Rosebud"); // pass string literal to
// PolyWidget::operator()
There is no way to do this with a C++11 lambda. In C++14, however, it's easily achieved via a lambda with an auto
parameter:
auto boundPW = [pw](const auto& param) // C++14
{ pw(param); };
These are edge cases, of course, and they're transient edge cases at that, because compilers supporting C++14 lambdas are increasingly common.
When bind
was unofficially added to C++ in 2005, it was a big improvement over its 1998 predecessors. The addition of lambda support to C++11 rendered std::bind
all but obsolete, however, and as of C++14, there are just no good use cases for it.
Things to Remember
• Lambdas are more readable, more expressive, and may be more efficient than using std::bind
.
• In C++11 only, std::bind
may be useful for implementing move capture or for binding objects with templatized function call operators.
Chapter 7
The Concurrency API
One of C++11's great triumphs is the incorporation of concurrency into the language and library. Programmers familiar with other threading APIs (e.g., pthreads or Windows threads) are sometimes surprised at the comparatively Spartan feature set that C++ offers, but that's because a great deal of C++'s support for concurrency is in the form of constraints on compiler-writers. The resulting language assurances mean that for the first time in C++'s history, programmers can write multithreaded programs with standard behavior across all platforms. This establishes a solid foundation on which expressive libraries can be built, and the concurrency elements of the Standard Library (tasks, futures, threads, mutexes, condition variables, atomic objects, and more) are merely the beginning of what is sure to become an increasingly rich set of tools for the development of concurrent C++ software.
In the Items that follow, bear in mind that the Standard Library has two templates for futures: std::future
and std::shared_future
. In many cases, the distinction is not important, so I often simply talk about futures , by which I mean both kinds.
Item 35: Prefer task-based programming to thread-based.
If you want to run a function doAsyncWork
asynchronously, you have two basic choices. You can create a std::thread
and run doAsyncWork
on it, thus employing a thread-based approach:
int doAsyncWork();
std::threadt(doAsyncWork);
Or you can pass doAsyncWork
to std::async
, a strategy known as task-based :
auto fut = std::async(doAsyncWork); // "fut" for "future"
In such calls, the function object passed to std::async
(e.g., doAsyncWork
) is considered a task .
The task-based approach is typically superior to its thread-based counterpart, and the tiny amount of code we've seen already demonstrates some reasons why. Here, doAsyncWork
produces a return value, which we can reasonably assume the code invoking doAsyncWork
is interested in. With the thread-based invocation, there's no straightforward way to get access to it. With the task-based approach, it's easy, because the future returned from std::async
offers the get
function. The get
function is even more important if doAsyncWork
emits an exception, because get
provides access to that, too. With the thread-based approach, if doAsyncWork
throws, the program dies (via a call to std::terminate
).
A more fundamental difference between thread-based and task-based programming is the higher level of abstraction that task-based embodies. It frees you from the details of thread management, an observation that reminds me that I need to summarize the three meanings of “thread” in concurrent C++ software:
Читать дальше