I have the following (contrived) code where I have a printer class with a single print function in it and a working class that processes a string and then calls a callback function to the print function:
#include <functional>
#include <iostream>
using callback_fn = std::function<bool(std::string)>;
class printer
{
public:
bool print(std::string data)
{
std::cout << data << std::endl;
return true;
}
};
class worker
{
public:
callback_fn m_callback;
void set_callback(callback_fn callback)
{
m_callback = std::move(callback); // <-- 1. callback is a temp, so what does std::move do here?
}
void process_data(std::string data)
{
if (!m_callback(data)) { /* do error handling */ }
}
};
int main() {
printer p;
worker w;
w.set_callback( std::move([&](std::string s){ return p.print(s); }) ); // <-- 2. what does std::move do here?
w.process_data("hello world2");
}
Note: I have std:: move()
called twice... now this works (surprisingly to me) but I have both only to show what I am trying. My questions are:
std::move()
in the set_callback()
function to "pull" out the temp, and if I use this is there really a copy or does std:: move(
) mean that its not really a copy?std:: move()
to pass in the lambda... and is that even correct.std:: moves()
... which implies that I still don't understand what std:: move()
is doing - so if someone can elighten me on what is going on here that would be great!My example can be seen here in wandbox: https://wandbox.org/permlink/rJDudtg602Ybhnzi
UPDATE The reason for me trying to use std::move was to avoid copying the lambda around. (I think that is called forwarding/perfect-forwarding)...but I think I am making a hash of it!
std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object. In particular, std::move produces an xvalue expression that identifies its argument t . It is exactly equivalent to a static_cast to an rvalue reference type.
That's pretty much the only time you should write std::move , because C++ already uses move automatically when copying from an object it knows will never be used again, such as a temporary object or a local variable being returned or thrown from a function. That's it.
In this line,
w.set_callback( std::move([&](std::string s){ return p.print(s); }) );
you cast an rvalue to an rvalue. This is a no-op and hence pointless. Passing a temporary to a function that accepts its parameter by value is fine by default. The function argument is likely to be instantiated in place anyhow. In the worst case, it is move-constructed, which doesn't require an explicit call to std::move
on the function argument - again, as it is already an rvalue in your example. To clarify the situation, consider this different scenario::
std::function<bool(std::string)> lValueFct = [&](std::string s){ /* ... */ }
// Now it makes sense to cast the function to an rvalue (don't use it afterwards!)
w.set_callback(std::move(lValueFct));
Now for the other case. In this snippet
void set_callback(callback_fn callback)
{
m_callback = std::move(callback);
}
you move-assign to m_callback
. This is fine, as the parameter is passed by value and not used afterwards. One good resource on this technique is Item 41 in Eff. Modern C++. Here, Meyers also points out, however, that while it's generally fine to employ pass-by-value-then-move-construct for initialization, it's not necessarily the best option for assignment, because the by-value parameter must allocate internal memory to hold the new state, while that could use an existing buffer when directly copied from a const
-qualified reference function parameter. This is exemplified for std::string
arguments, and I'm not sure how this can be transferred to std::function
instances, but as they erase the underlying type, I could imagine this being an issue, especially for larger closures.
I guess I don't understand why this code works with two std::moves... which implies that I still don't understand what std::move is doing
std::move
is there to make it explicit in cases you are intending to move from an object. Move semantics are designed to work with rvalues. Thus, std::move()
takes any expression (such as an lvalue) and makes an rvalue out of it. This usage arises typically when you need to allow an lvalue to be passed to the function overloads that accept rvalue reference as an argument, such as move constructors and move assignment operators. The idea of moving is to efficiently be transferring resources instead of making copies.
In your snippet you're not using std::move()
in an invalid way, hence this code works. In the rest of the answer we try to see whether this usage is advantageous or not.
Should I use std::move to pass in the lambda
Seemingly no, you have no reason of doing that in the snippet. First of all, you are calling move()
on a what is already an rvalue. Further, syntactically, set_callback()
is receiving its std::function<bool(std::string)>
argument by value, of which your lambda is initializing an instance just fine at present.
Should I use std::move in the set_callback() function
It isn't 100% clear what you are gaining by using the move version of the assignment operator onto the m_callback
member variable, instead of the regular assignment. It won't cause any undefined behavior though, as you are not trying to use the argument after moving it. Also, since C++11 the callback
parameter in set_callback()
will be move constructed for rvalues such as your temporary, and copy constructed for an lvalue, such as if you'd call it like this:
auto func = [&](std::string s){ return p.print(s); };
w.set_callback(func);
What you need to be considering is whether inside the method, moving is better then copying in your case. Moving involves its own implementation of the move assignment for the relevant type. I'm not just saying QOI here, but consider that when moving you need to release whatever resource m_callback
was holding up to that point, and for the scenario of moving from a constructed instance (as we've covered that callback
has been either copy constructed or move-constructed from its argument), that's adding to the cost this construction already had. Not sure that such a moving overhead applies in your case, but still your lambda is a not obviously expensive to copy as it is. That said, opting for two overloads, one taking a const callback_fn& callback
and copy-assigning inside and one taking a callback_fn&& callback
and move-assigning inside would allow to mitigate this potential issue altogether. As in either one you don't construct anything for the parameter and overall you're not necessarily releasing old resources as an overhead, as when performing the copy-assignment, one can potentially use the already existing resources of the LHS by copying onto them instead of releasing it prior to moving the ones from the RHS.
I know that I can pass by value, but my goal was to move the temp so that I don't have a copy of it (perfect forwarding?)
In the context of type deduction (template
or auto
), a T&&
is a forwarding reference, not an rvalue reference. As such you only have to write the function once (template function, no overloads), and relying internally on std::forward
(equivalent to static_cast<T&&>
) will make sure that in any use-case, the above described path for using the two overloads is preserved in terms of the cost being a copy-assignment for an lvalue call and a move-assignment for an rvalue call:
template<class T>
void set_callback(T&& callback)
{
m_callback = std::forward<T>(callback);
}
According to c++ reference std::move
is just a cast to rvalue reference.
std::move
inside your set_callback
method. std::function
is CopyConstructible and CopyAssignable (docs), so you can just write m_callback = callback;
. If you use std::function
move constructor, the object you moved from will be "unspecified" (but still valid), in particular it might be empty (which doesn't matter because it is a temporary). You can also read this topic.set_callback
, so it doesn't matter if you move it or copy it.Below is the example with the situation when std::move
makes a difference:
callback_fn f1 = [](std::string s) {std::cout << s << std::endl; return true; };
callback_fn f2 = f1;
f1("xxx"); // OK, invokes previously assigned lambda
f2("xxx"); // OK, invokes the same as f1
f2 = std::move(f1);
f1("xxx"); // exception, f1 is unspecified after move
Output:
xxx
xxx
C++ exception with description "bad_function_call" thrown in the test body.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With