Почему экземпляры std :: function имеют конструктор по умолчанию? - PullRequest
13 голосов
/ 23 сентября 2011

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

Если вы определили функцию 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 :: function, вы все равно можете передать на него указатель (или использовать boost :: необязательный).

Ответы [ 7 ]

15 голосов
/ 23 сентября 2011

Верно, но это также верно для других типов.Например, если я хочу, чтобы в моем классе был необязательный объект Person, я делаю свой член данных указателем Person.Почему бы не сделать то же самое для 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 содержит функцию.Это держатель для вызываемого типа.Следовательно, существует вероятность, что он не содержит вызываемого типа.

9 голосов
/ 23 сентября 2011

На самом деле ваше приложение не должно аварийно завершать работу.

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

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

Поведение задано идеально.

5 голосов
/ 23 сентября 2011

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

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

5 голосов
/ 23 сентября 2011

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

1 голос
/ 23 сентября 2011

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

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

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

0 голосов
/ 26 сентября 2015

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

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);
0 голосов
/ 23 сентября 2011

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

Cheers & hth.,

...