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

Альтернатива std:: function для передачи функции как аргумента (обратные вызовы и т.д.)

Я наткнулся на это во время моих экспериментов с С++ 11. Я нахожу, что это очевидное решение, но я не смог найти других примеров этого в дикой природе, поэтому я обеспокоен тем, что там чего-то не хватает.

Практика, о которой я говорю (в функции "addAsync" ):

#include <thread>
#include <future>
#include <iostream>
#include <chrono>

int addTwoNumbers(int a, int b) {
    std::cout << "Thread ID: " << std::this_thread::get_id() << std::endl;

    return a + b;
}

void printNum(std::future<int> future) {
    std::cout << future.get() << std::endl;
}

void addAsync(int a, int b, auto callback(std::future<int>) -> void) { //<- the notation in question
    auto res = std::async(std::launch::async, addTwoNumbers, a, b);

    if (callback) //super straightforward nullptr handling
        return callback(std::move(res));
}

int main(int argc, char** argv) {
    addAsync(10, 10, [](std::future<int> number) { //lambda functions work great
        addAsync(number.get(), 20, [](std::future<int> number) {
            addAsync(893, 4387, printNum); //as do standard functions
            addAsync(2342, 342, nullptr); //executes, sans callback

            std::cout << number.get() << std::endl;
        });
    });

    std::cout << "main thread: " << std::this_thread::get_id() << std::endl;

    return 0;
}

Является ли это плохой практикой или не переносится (я только пробовал это в MSVС++ 2015)? Кроме того, как компилятор относится к этому; путем преобразования в std:: function?

Я хотел бы продолжать использовать это в своих проектах, поскольку он, очевидно, указывает необходимые типы аргументов и тип возвращаемого значения в "подписи", принимает nullptr для опциональности и, кажется, "просто работает" (я знаю, что эти являются известными последними словами в С++).

4b9b3361

Ответ 1

Используется необработанный указатель на функцию.

В отличие от std::function, это не будет работать с lambda, которая захватывает или с результатом std::bind, или с общим типом класса, который реализует operator().

Ответ 2

auto callback(std::future<int>) -> void - это объявление объекта типа void(std::future<int>), называемого callback. При указании в качестве аргумента компилятор настраивает это как указатель на функцию типа void(*)(std::future<int>).

Ваша лямбда является апатридом и как таковая может быть неявно преобразована в указатель функции.

Как только вы добавите нетривиальный захват, ваш код перестанет компилироваться:

[argc](std::future<int> number) { 
   std::cout << argc << '\n';

...

Теперь, игнорируя ваш вопрос и просматривая заголовок...

Существует скромная стоимость для std::function, потому что это тип значения, а не тип представления. Как тип значения, он фактически копирует свой аргумент.

Вы можете обойти это, обернув вызывающий объект в std::ref, но если вы хотите указать "Я не буду поддерживать этот объект функции дольше, чем этот вызов", вы можете написать тип function_view как следующим образом:

template<class Sig>
struct function_view;

template<class R, class...Args>
struct function_view<R(Args...)> {
  void* ptr = nullptr;
  R(*pf)(void*, Args...) = nullptr;

  template<class F>
  using pF = decltype(std::addressof( std::declval<F&>() ));

  template<class F>
  void bind_to( F& f ) {
    ptr = (void*)std::addressof(f);
    pf = [](void* ptr, Args... args)->R{
      return (*(pF<F>)ptr)(std::forward<Args>(args)...);
    };
  }
  // when binding to a function pointer
  // even a not identical one, check for
  // null.  In addition, we can remove a
  // layer of indirection and store the function
  // pointer directly in the `void*`.
  template<class R_in, class...Args_in>
  void bind_to( R_in(*f)(Args_in...) ) {
    using F = decltype(f);
    if (!f) return bind_to(nullptr);
    ptr = (void*)f;
    pf = [](void* ptr, Args... args)->R{
      return (F(ptr))(std::forward<Args>(args)...);
    };
  }
  // binding to nothing:
  void bind_to( std::nullptr_t ) {
    ptr = nullptr;
    pf = nullptr;
  }       
  explicit operator bool()const{return pf;}

  function_view()=default;
  function_view(function_view const&)=default;
  function_view& operator=(function_view const&)=default;

  template<class F,
    std::enable_if_t< !std::is_same<function_view, std::decay_t<F>>{}, int > =0,
    std::enable_if_t< std::is_convertible< std::result_of_t< F&(Args...) >, R >{}, int> = 0
  >
  function_view( F&& f ) {
    bind_to(f); // not forward
  }

  function_view( std::nullptr_t ) {}

  R operator()(Args...args) const {
      return pf(ptr, std::forward<Args>(args)...);
  }
};

живой пример.

Это также полезно в том, что это строго более простой тип стирания стилей, чем std::function, поэтому он мог бы просвещать его.

Ответ 3

Альтернатива функции std:: для передачи функции как аргумента

Одним из вариантов может быть указатель на функцию (включая указатель на функцию-член). Но std::function намного лучше (IMO).