Подтвердить что ты не робот

Разрешено ли `std:: function` перемещать свои аргументы?

Во время работы над этим вопросом я заметил, что реализация GCC (v4.7) std::function перемещает свои аргументы, когда они берутся по значению. Следующий код показывает это поведение:

#include <functional>
#include <iostream>

struct CopyableMovable
{
    CopyableMovable()                        { std::cout << "default" << '\n'; }
    CopyableMovable(CopyableMovable const &) { std::cout << "copy" << '\n'; }
    CopyableMovable(CopyableMovable &&)      { std::cout << "move" << '\n'; }
};

void foo(CopyableMovable cm)
{ }

int main()
{
    typedef std::function<void(CopyableMovable)> byValue;

    byValue fooByValue = foo;

    CopyableMovable cm;
    fooByValue(cm);
}
// outputs: default copy move move

Мы видим здесь, что выполняется копия cm (что представляется разумным, так как параметр byValue принимается по значению), но затем есть два хода. Поскольку function работает с копией cm, тот факт, что он перемещает свой аргумент, можно рассматривать как несущественную деталь реализации. Однако это поведение вызывает некоторые проблемы при использовании function вместе с bind:

#include <functional>
#include <iostream>

struct MoveTracker
{
    bool hasBeenMovedFrom;

    MoveTracker()
      : hasBeenMovedFrom(false)
    {}
    MoveTracker(MoveTracker const &)
      : hasBeenMovedFrom(false)
    {}
    MoveTracker(MoveTracker && other)
      : hasBeenMovedFrom(false)
    {
        if (other.hasBeenMovedFrom)
        {
            std::cout << "already moved!" << '\n';
        }
        else
        {
            other.hasBeenMovedFrom = true;
        }
    }
};

void foo(MoveTracker, MoveTracker) {}

int main()
{
    using namespace std::placeholders;
    std::function<void(MoveTracker)> func = std::bind(foo, _1, _1);
    MoveTracker obj;
    func(obj); // prints "already moved!"
}

Разрешено ли это поведение стандартом? Разрешено ли std::function перемещать свои аргументы? И если да, то нормально ли, что мы можем преобразовать оболочку, возвращаемую bind в std::function с параметрами по значению, даже если это вызывает неожиданное поведение при работе с несколькими вхождениями заполнителей?

4b9b3361

Ответ 1

std::function указывается для передачи предоставленных аргументов завернутой функции с помощью std::forward. например для std::function<void(MoveTracker)> оператор вызова функции эквивалентен

void operator(CopyableMovable a)
{
    f(std::forward<CopyableMovable>(a));
}

Так как std::forward<T> эквивалентно std::move, когда T не является ссылочным типом, это учитывает один из шагов в вашем первом примере. Возможно, что второе происходит из-за того, что нужно пройти через слои косвенности внутри std::function.

В этом случае также возникает проблема, с которой вы сталкиваетесь с использованием std::bind в качестве обернутой функции: std::bind имеет значение и для перенаправления своих параметров, и в этом случае он передается rvalue в результате вызова std::forward внутри std::function. Таким образом, оператор вызова функции вашего выражения bind передает ссылку rvalue на каждый из аргументов. К сожалению, поскольку вы повторно использовали местозаполнитель, это ссылка rvalue на тот же объект в обоих случаях, поэтому для подвижных типов, в зависимости от того, что построено, сначала будет перемещаться значение, а второй параметр получит пустую оболочку.