Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Move semantic with std::function

Tags:

std::function provides a constructor from an rvalue ref. What happens to the moved function object by standard? Will it be empty so that calling it again has no effects?

like image 779
Martin Avatar asked Dec 03 '12 09:12

Martin


People also ask

Can you std move std function?

Yes, unless you catch it, or unless you mark the destructor noexcept(false) . And in the latter case you have to be pretty careful or it may result in std::terminate() anyway.

Can you std :: move a reference?

"std::move" should only be used where moving can happenWhen passing the result of std::move as a const reference argument. In this case, no object will be moved since it's impossible to call the move constructor from within the function.

What does move () do in C ++?

std::move in C++Moves the elements in the range [first,last] into the range beginning at result. The value of the elements in the [first,last] is transferred to the elements pointed by result. After the call, the elements in the range [first,last] are left in an unspecified but valid state.

Does std copy move?

std::move is actually just a request to move and if the type of the object has not a move constructor/assign-operator defined or generated the move operation will fall back to a copy.


2 Answers

There is too much confusion around this question. I'm going to try to lay things out clearly...

This section describes the moved-from state of std-defined objects:

17.6.5.15 [lib.types.movedfrom]

Objects of types defined in the C++ standard library may be moved from (12.8). Move operations may be explicitly specified or implicitly generated. Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.

What does this mean? This means that given a std-defined moved-from object, you can do anything with that object that doesn't require a priori knowledge of the state of that object. The class of actions that require no a priori knowledge of the current state are those that have no preconditions.

For example you can call clear() on a moved-from vector because there are no preconditions on vector::clear(). But you can't call pop_back() because that does have preconditions.

Looking specifically at the call operator of function:

20.8.11.2.4 [func.wrap.func.inv]

R operator()(ArgTypes... args) const 

Effects: INVOKE(f, std::forward(args)..., R) (20.8.2), where f is the target ob- ject (20.8.1) of *this.

Returns: Nothing if R is void, otherwise the return value of INVOKE (f, std::forward( args)..., R).

Throws: bad_function_call if !*this; otherwise, any exception thrown by the wrapped callable object.

Note that there is no precondition or Requires clause. That means that calling the call operator of function of a moved-from function is not undefined behavior. No matter what state the function is in, you're not going to violate any preconditions with this call.

Note that in no case does the specification say that the call will have no effect. So having no effect is not a possibility.

The call will either call the wrapped function, or throw a bad_function_call. Those are the only two choices. And which behavior it has depends on the state of the function object. And the state of the function object is unspecified ([lib.types.movedfrom]).

like image 131
Howard Hinnant Avatar answered Dec 25 '22 01:12

Howard Hinnant


Under 20.8.11.2.1p6, function(function &&f) leaves f in a valid state with an unspecified value.

The empty state is a valid state, so you should expect that the moved-from function object can be empty.

Because function performs type erasure, and function objects can be arbitrarily expensive, the optimisation to leave the moved-from object empty makes sense:

std::function<void()> g{std::bind{f, std::array<int, 1000>{}}}; std::function<void()> h{std::move{g}}; 

After h has been constructed by move from g, one would expect the contained bind have been transferred from g to h rather than copying, so g would be left empty.

For the following program, gcc 4.5.1 prints empty:

#include <functional> #include <iostream> void f() {} int main() {     std::function<void()> g{f}, h{std::move(g)};     std::cout << (g ? "not empty\n" : "empty\n"); } 

This is not necessarily the most optimal behaviour; inlining small callables (e.g. function pointers) creates a situation where copying the callable is more efficient than moving it and emptying the moved-from object, so another implementation could leave g in a non-empty callable state.

like image 43
ecatmur Avatar answered Dec 25 '22 02:12

ecatmur