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

Почему экземпляры std:: function имеют конструктор по умолчанию?

Это, вероятно, философский вопрос, но я столкнулся с следующей проблемой:

Если вы определяете функцию std:: и не инициализируете ее правильно, ваше приложение будет разбиваться, например:

typedef std::function<void(void)> MyFunctionType;
MyFunctionType myFunction;
myFunction();

Если функция передается как аргумент, например:

void DoSomething (MyFunctionType myFunction)
   {
   myFunction();
   }

Затем, конечно, он также падает. Это означает, что я вынужден добавить контрольный код следующим образом:

void DoSomething (MyFunctionType myFunction)
   {
   if (!myFunction) return;
   myFunction();
   }

Требование этих проверок дает мне флэш-обратную связь со старыми днями C, где вам также необходимо было явно проверить все аргументы указателя:

void DoSomething (Car *car, Person *person)
   {
   if (!car) return;      // In real applications, this would be an assert of course
   if (!person) return;   // In real applications, this would be an assert of course
   ...
   }

К счастью, мы можем использовать ссылки на С++, что мешает мне писать эти проверки (предполагая, что вызывающий объект не передал содержимое nullptr функции:

void DoSomething (Car &car, Person &person)
   {
   // I can assume that car and person are valid
   }

Итак, почему экземпляры std:: function имеют конструктор по умолчанию? Без конструктора по умолчанию вам не нужно будет добавлять проверки, как и для других нормальных аргументов функции. И в тех редких случаях, когда вы хотите передать "необязательную" std:: функцию, вы все равно можете передать указатель на нее (или использовать boost:: optional).

4b9b3361

Ответ 1

Правда, но это верно и для других типов. Например. если я хочу, чтобы мой класс имел необязательное Person, тогда я делаю свой элемент данных Person-pointer. Почему бы не сделать то же самое для std:: functions? Что особенного в std:: function, что оно может иметь "недопустимое" состояние?

У него нет "недопустимого" состояния. Он больше недействителен:

std::vector<int> aVector;
aVector[0] = 5;

У вас есть пустой function, так же как aVector - пустой vector. Объект находится в очень четко определенном состоянии: состояние отсутствия данных.

Теперь рассмотрим предложение "указатель на функцию":

void CallbackRegistrar(..., std::function<void()> *pFunc);

Как вы это называете? Ну, здесь одна вещь, которую вы не можете сделать:

void CallbackFunc();
CallbackRegistrar(..., CallbackFunc);

Это не допускается, потому что CallbackFunc - это функция, а тип параметра - std::function<void()>*. Эти два не являются конвертируемыми, поэтому компилятор будет жаловаться. Поэтому, чтобы выполнить вызов, вы должны сделать это:

void CallbackFunc();
CallbackRegistrar(..., new std::function<void()>(CallbackFunc));

Вы только что ввели new в изображение. Вы выделили ресурс; кто будет отвечать за это? CallbackRegistrar? Очевидно, что вы можете использовать какой-то умный указатель, поэтому вы еще больше загромождаете интерфейс:

void CallbackRegistrar(..., std::shared_ptr<std::function<void()>> pFunc);

Это много раздражения API и крутизны, просто чтобы пройти функцию. Самый простой способ избежать этого - разрешить std::function быть пустым. Так же, как разрешить std::vector быть пустым. Так же, как мы разрешаем std::string быть пустым. Так же, как мы разрешаем std::shared_ptr быть пустым. И так далее.

Проще говоря: std::function содержит функцию. Это держатель для вызываемого типа. Следовательно, существует вероятность, что он не содержит вызываемого типа.

Ответ 2

Собственно, ваше приложение не должно терпеть крах.

§ 20.8.11.1 Класс bad_function_call [func.wrap.badcall]

1/ Исключение типа bad_function_call выбрано function::operator() (20.8.11.2.4), когда объект-оболочка функции не имеет цели.

Поведение отлично определено.

Ответ 3

Одним из наиболее распространенных вариантов использования std::function является регистрация обратных вызовов, которые должны быть вызваны при выполнении определенных условий. Разрешение неинициализированных экземпляров позволяет регистрировать обратные вызовы только тогда, когда это необходимо, иначе вам придется всегда передавать хотя бы некоторую функцию no-op.

Ответ 4

Ответ, вероятно, исторический: std::function означает замену указателей на функции, а указатели функций имеют возможность NULL. Таким образом, если вы хотите предложить легкую совместимость с указателями функций, вам нужно предложить недопустимое состояние.

Определяемое недопустимое состояние действительно не требуется, поскольку, как вы упомянули, boost::optional делает эту работу просто отлично. Поэтому я бы сказал, что std::function есть только ради истории.

Ответ 5

Есть случаи, когда вы не можете инициализировать все при построении (например, когда параметр зависит от влияния на другую конструкцию, которая, в свою очередь, зависит от эффекта от первого...).

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

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

Ответ 6

Точно так же, как вы можете добавить nullstate к типу-функтору, у которого его нет, вы можете обернуть функтор классом, который не допускает nullstate. Первый требует добавления состояния, последнее не требует нового состояния (только ограничение). Таким образом, хотя я не знаю обоснования дизайна std::function, он поддерживает наиболее скудное и среднее использование, независимо от того, что вы хотите.

Приветствия и hth.,

Ответ 7

Вы просто используете функцию std:: для обратных вызовов, вы можете использовать простую вспомогательную функцию шаблона, которая пересылает свои аргументы обработчику, если он не пуст:

template <typename Callback, typename... Ts>
void SendNotification(const Callback & callback, Ts&&... vs)
{
    if (callback)
    {
        callback(std::forward<Ts>(vs)...);
    }
}

И используйте его следующим образом:

std::function<void(int, double>> myHandler;
...
SendNotification(myHandler, 42, 3.15);