Почему экземпляры 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
...
}
К счастью, мы можем использовать ссылки в C++, что не позволяет мне писать эти проверки (предполагая, что вызывающий объект не передавал содержимое nullptr функции:
void DoSomething (Car &car, Person &person)
{
// I can assume that car and person are valid
}
Итак, почему экземпляры std:: function имеют конструктор по умолчанию? Без конструктора по умолчанию вам не пришлось бы добавлять проверки, как и для других, обычных аргументов функции. И в тех "редких" случаях, когда вы хотите передать "необязательную" функцию std::, вы все еще можете передать на него указатель (или использовать boost::необязательно).
7 ответов:
Он не имеет "недействительного" состояния. Он не более недействителен, чем этот:Верно, но это также верно и для других типов. Например, если я хочу, чтобы в моем классе был необязательный человек, то я делаю свой элемент данных указателем на человека. Почему бы не сделать то же самое для функций std::? Что такого особенного в функции std::, что она может иметь "недопустимое" состояние?
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
содержит функцию. Это держатель для вызываемого типа. Поэтому существует вероятность, что он не содержит вызываемого типа.
На самом деле ваше приложение не должно аварийно завершиться.
§ 20.8.11.1 класс bad_function_call [func.заворачивать.badcall]
1/ исключение типа
bad_function_call
вызываетсяfunction::operator()
(20.8.11.2.4), когда объект-оболочка функции не имеет цели.Поведение совершенно определено.
Один из наиболее распространенных вариантов использования
std::function
- это регистрация обратных вызовов, вызываемых при выполнении определенных условий. Учет неинициализированных экземпляров позволяет регистрировать обратные вызовы только в случае необходимости, в противном случае вы были бы вынуждены всегда передавать хотя бы какую-то функцию no-op.
Ответ, вероятно, исторический:
Идентифицируемое недействительное состояние на самом деле не нужно, так как, как вы упомянули,std::function
подразумевается как замена указателей функций, а указатели функций имели возможность бытьNULL
. Поэтому, когда вы хотите предложить легкую совместимость указателям функций, вам нужно предложить недопустимое состояние.boost::optional
делает эту работу просто отлично. Поэтому я бы сказал, чтоstd::function
существуют только ради истории.
Бывают случаи, когда вы не можете инициализировать все при построении (например, когда параметр зависит от влияния на другую конструкцию, которая в свою очередь зависит от влияния на первую ...).
В этом случае вы должны обязательно разорвать цикл, допустив идентифицируемое недопустимое состояние, которое будет исправлено позже. Таким образом, вы строите первый элемент как "null", строите второй элемент и переназначаете первый.
Вы можете, на самом деле, избежать проверок, если-где функция используется-вы даете, что внутри конструктора объекта, который его вставляет, вы всегда будете возвращаться после допустимого переназначения.
Точно так же, как вы можете добавить nullstate к типу функтора, у которого его нет, вы можете обернуть функтор с классом, который не допускает nullstate. Первый требует добавления состояния, второй не требует нового состояния (только ограничение). Таким образом, хотя я не знаю обоснования дизайна
std::function
, он поддерживает наиболее бережливое и среднее использование, независимо от того, что вы хотите.Ура & ХТ.,
Вы просто используете функцию 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);