Перемещение 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?
Ответы
Ответ 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
.