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

Перемещение std:: function в другую std:: function не вызывает конструктор перемещения на захваченных переменных

У меня есть класс A, который выводит сообщение при создании/копировании/перемещении

class A
{
public:
    A(std::string s)
        :s_(s)
    {
        std::cout << "A constructed\n";
    }
    ~A()
    {
        std::cout << "A destructed\n";
    }
    A(const A& a)
        :s_(a.s_)
    {
        std::cout << "A copy constructed\n";
    }
    A(A&& a)
       :s_(std::move(a.s_))
    {
        std::cout << "A moved\n";
    }
    A& operator=(const A& a)
    {
        s_ = a.s_;
        std::cout << "A copy assigned\n";
    }
    A& operator=(A&& a)
    {
        s_ = std::move(a.s_);
        std::cout << "A move assigned\n";
    }

    std::string s_;
};

В main, я создаю экземпляр A, фиксирую его в лямбда по значению, скопируйте этот лямбда в std::function и, наконец, переместите, который std::function в другой std::function

int main()
{
    A a("hello ");
    std::function<void()> f = [a]{ std::cout << a.s_; };
    std::function<void()> g(std::move(f));
}

Отпечатано следующее

A constructed
A copy constructed
A copy constructed
A destructed
A destructed
A destructed

Почему конструктор перемещения A не вызывается? Не должен ли последний шаг перемещения f в g вызвать A move constructor?

4b9b3361

Ответ 1

Конструктор копирования не вызван именно потому, что вы переместили std::function. Это связано с тем, что std::function может дополнительно сохранять захваченные значения в куче и сохранять указатель на них. Таким образом, перемещение функции просто требует перемещения этого внутреннего указателя. Очевидно, что MSVC выбирает сохранение захватов в куче и GCC и т.д., Чтобы сохранить их в стеке, тем самым требуя также перемещения захваченных значений.

Изменить: Благодаря Mooing Duck для указания в комментарий к вопросу, что GCC также сохраняет захваты в куче. Фактически разница в том, что GCC перемещает захваты от лямбда до std::function, когда он построен из лямбда.

Ответ 2

В этой стандартной реализации библиотеки не используется оптимизация малого буфера в этом случае, поэтому ваша функция f содержит указатель на область выделенной кучи памяти, в которой хранится копия a. Поскольку вы перемещаете f в g, нет причин для выполнения глубокой копии, и реализация может просто переместить право собственности на функцию, хранящуюся в f, на g (например, unique_ptr).

В связи с тем, что здесь не используется небольшой буфер, это может быть связано с тем, что ваша реализация определяет конструктор перемещения function как noexcept .

Если конструктор перемещения function не является исключением, он не может вызывать любую функцию, которая может быть выбрана, поэтому реализация просто отказывается перемещать ваш объект (из небольшого буфера f в g) и выделяет это в куче, чтобы он мог просто перемещать указатель в конструкторе/присваивании перемещения.

Оба libstd++ и libc++ генерируют вызов конструктора копирования в строке g = move(f), если вы просто добавляете конструктор noexcept в a copy. Удивительно, что оба они, похоже, игнорируют наличие конструктора перемещения noexcept.


† Обратите внимание, что (по крайней мере, в последнем черновике) стандартные мандаты function(function&&) должны быть non noexcept, но оба libstd ++ и libС++ реализуют его как noexcept, я не могу проверить из MSVC в настоящий момент.

Ответ 3

Похоже на слабость MSVC std::function move constructor. Я пробовал ваш код на Clang 3.3 и вызывает конструктор move A.