简体   繁体   中英

What is the exception specification of std::function's move constructor?

I've looked at cppreference.com and they seem to indicate no noexcept specification on std::function(std::function&&) . This seems rather odd to me. Does the standard really not give a nothrow guarantee in this case?

Quoting the standard (as you request):

C++11 §20.8.11.2.1/6 (from N3290):
function(function&& f);
template <class A> function(allocator_arg_t, const A& a, function&& f);
Effects: If !f , *this has no target; otherwise, move-constructs the target of f into the target of *this , leaving f in a valid state with an unspecified value.

So, sorry, no noexcept on the move constructor.

There is a related Defect Report 2062 which is still open, but it goes in the other direction, so to speak, namely that there is at least one noexcept that apparently should not be there, for whatever the rationale of that is…

It may be that the intent is to support callables that have throwing move constructors. But that is just speculation in the direction of rationalization. For example, imagine reallocation of the buffer in a vector of such function objects, where an attempt is made to move the originals, and where in the middle somewhere one of them throws (I think this was the original example by Abrahams et.al). Oh dang, they can't guaranteed be moved back, and neither can we go forward. So the reallocation fails, and the operation that caused the reallocation, fails, with the vector in an invalid state . A requirement of non-throwing move of the callable objects would have supported such general usage of function objects, including optimized vector reallocation (etc.). Which IMHO makes it doubtful that the intent really has been to make this trade-off.

None of the other answers really make sense to me. std::function does have a noexcept default constructor and a noexcept swap method, so an implementor could always define the move constructor like so:

function(function&& other) noexcept
  : function()
{
  swap(*this, other);
}

Given the above, I'm not sure why the committee declined to make the move constructor noexcept . In any case, you can work around the problem by creating a wrapper to hold the std::function and move it using this technique.

I imagine that the function object is capable of storing an arbitrary, user-defined, callable object. When you move the function object, that contained, user-defined object is moved as well, and there are no guarantees that this can be done without exceptions.

As others said the std::function move constructor is NOT noexcept by the C++ standard.

As Peter Ruderman said, an implementor can define noexcept move constructor using swap method (which is noexcept by the standard).

And the GCC implementation actually uses this technique and defines the std::function move constructor as noexcept :

/**
 *  @brief %Function move constructor.
 *  @param __x A %function object rvalue with identical call signature.
 *
 *  The newly-created %function contains the target of @a __x
 *  (if it has one).
 */
function(function&& __x) noexcept : _Function_base()
{
  __x.swap(*this);
}

So if you use GCC , you can assume that the std::function move constructor is noexcept . But other implementation of the C++ standard library may have a different implementation of the move constructor. So you must not rely on that.

It is better to have std::function default constructed ( noexcept ) and then use swap method:

    // the line below could throw
    // std::function<void()> my_function(std::move(the_function_to_move_from));

    // the code below is noexcept
    std::function<void()> my_function;
    my_function.swap(the_function_to_move_from);

It is not as graceful as using move constructor but at least it is portable.

And beware of move semantic!!! It may do something you don't expect because it is usually specified to leave the object moved from in a valid but unspecified state. For example:

#include <iostream>
#include <functional>

struct A
{
    void call()
    {
        if (fn) fn();
    }

    std::function<void()> fn;
};

int main()
{
    std::function<void()> hello = []() { std::cerr << "Hello world" << std::endl; };
    A a;
    hello = std::move(a.fn);
    a.call(); // may print "Hello world". Is it what you expect?

    return 0;
}

The GCC implementation doesn't print "Hello world" , but other implementation may do. The right way is to explicitly clear the object moved from:

hello = std::move(a.fn);
a.fn = nullptr;

But it makes move semantic inconvenient to use (IMO).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM