Почему экземпляры 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 13

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);