Переместить семантику с помощью std::function

std::function предоставляет конструктор из значения r. Что происходит с перемещенным функциональным объектом по стандарту? Будет ли он пустым, чтобы повторный вызов не имел никакого эффекта?

4 ответа

Решение

Под 20.8.11.2.1p6, function(function &&f) листья fв допустимом состоянии с неопределенным значением.

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

Так как function выполняет стирание типа, и функциональные объекты могут быть сколь угодно дорогими, поэтому имеет смысл оптимизировать оставление удаленного объекта пустым:

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

После h был построен движением от gможно было бы ожидать, что содержимое bind были переведены из g в h а не копирование, так g будет оставлено пустым.

Для следующей программы gcc 4.5.1 печатает 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");
}

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

Вокруг этого вопроса слишком много путаницы. Я собираюсь попытаться ясно изложить вещи...

В этом разделе описывается состояние перемещения объектов, определенных в std:

17.6.5.15 [lib.types.movedfrom]

Объекты типов, определенных в стандартной библиотеке C++, могут быть перемещены из (12.8). Операции перемещения могут быть явно указаны или неявно сгенерированы. Если не указано иное, такие перемещенные объекты должны быть помещены в допустимое, но неопределенное состояние.

Что это значит? Это означает, что с заданным в std перемещенным объектом можно делать с этим объектом все, что не требует априорного знания состояния этого объекта. К классу действий, которые не требуют априорных знаний о текущем состоянии, относятся те, которые не имеют предварительных условий.

Например, вы можете позвонить clear() на переезде vector потому что нет никаких предварительных условий на vector::clear(), Но ты не можешь позвонить pop_back() потому что у этого есть предпосылки.

Смотря конкретно на оператора звонка function:

20.8.11.2.4 [func.wrap.func.inv]

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

Эффекты: INVOKE(f, std::forward(args)..., R) (20.8.2), где f - целевой объект (20.8.1) *this.

Возвращает: Ничего, если R пусто, в противном случае возвращается значение INVOKE (f, std::forward( args)..., R).

Броски: bad_function_call if!* This; в противном случае - любое исключение, создаваемое завернутым вызываемым объектом.

Обратите внимание, что нет предусловия или условия "Требуется". Это означает, что вызов оператора вызова function отошли от function не неопределенное поведение. Неважно, в каком состоянии function в этом, вы не собираетесь нарушать какие-либо предварительные условия с этим вызовом.

Обратите внимание, что ни в коем случае в спецификации не говорится, что вызов не будет иметь никакого эффекта. Так что не иметь никакого эффекта не возможно.

Вызов либо вызовет упакованную функцию, либо выдает bad_function_call, Это единственные два варианта. И какое поведение он имеет, зависит от состояния function объект. И состояние function объект не указан ([lib.types.movedfrom]).

Что происходит с перемещенным функциональным объектом по стандарту?

Он будет в допустимом состоянии (таким образом, объект может использоваться), но фактическое состояние, в котором он находится, не определено. Последняя часть означает, что вызов любой функции, которая требует, чтобы объект находился в определенном состоянии, не обязательно будет работать.

Будет ли он пустым, чтобы повторный вызов не имел никакого эффекта?

Вы не можете предполагать, что это будет. Вызов функции требует, чтобы у нее была функция для вызова. Это часть его состояния. А так как состояние не указано, результаты его вызова не определены.

Если вы хотите снова использовать объект осмысленно, просто создайте новый function и назначьте это ему:

function<...> old;
function<...> new_ = std::move(old);
old = function<...>(...); //Reset to known state.
old(...); //Call is well-defined.

[Func.wrap.func.con]:

function(function&& f);
template <class A> function(allocator_arg_t, const A& a, function&& f);

Эффекты: если! F, * это не имеет цели; в противном случае, перемещение-создает цель f в цель *this, оставляя f в допустимом состоянии с неопределенным значением.

Другие вопросы по тегам