Понимание функции обратного вызова C++

0 Предисловие

После прочтения некоторых вступлений это кажется слишком официальным. Мое простое понимание состоит в том, что, исходя из идеи проектирования шаблонов, цель функции обратного вызова состоит в том, чтобы извлечь изменяющиеся модули и задержать реализацию; фиксированные модули абстрагируются и проектируются, а «жестко закодированный».

Пожалуйста, добавьте описание изображения

1. Как реализовать функцию обратного вызова

Вставьте сюда описание изображения

2. Обычные функции реализованы в виде указателей на функции.

#include <iostream>

// ===========不变的模块,写死================
// 回调函数类型
typedef void (*CallbackFunction)(int);
// 带回调函数参数的函数
void PerformOperation(int value, CallbackFunction callback) {
    
    
    std::cout << "Performing operation with value: " << value << std::endl;
    // 执行某些操作...

    // 调用回调函数
    callback(value * 2);
}


// =========变化的模块,放到客户端进行实现========
// 回调函数的实现
void Callback(int result) {
    
    
    std::cout << "Callback received result: " << result << std::endl;
    // 执行回调操作...
}

int main() {
    
    
    int value = 5;

    // 调用带回调函数参数的函数
    PerformOperation(value, Callback);

    return 0;
}

3. Функции-члены класса реализованы как статические функции.

#include <iostream>

// ===========不变的模块,写死================
// 定义回调函数类型
typedef void (*CallbackFunction)(int);
// 带回调函数参数的函数
void PerformOperation(int value, CallbackFunction callback) {
    
    
    std::cout << "Performing operation with value: " << value << std::endl;
    // 执行某些操作...

    // 调用回调函数
    callback(value * 2);
}


// =========变化的模块,放到客户端进行实现========
// 回调类
class CallbackClass {
    
    
public:
	// 类的静态成员函数,当做全局函数使用
    static void Callback(int result) {
    
    
        std::cout << "Callback received result: " << result << std::endl;
        // 执行回调操作...
    }
};

int main() {
    
    
    int value = 5;
    // 创建回调类的对象
    CallbackClass callbackObj;
    // 将静态成员函数作为回调函数
    CallbackFunction callback = CallbackClass::Callback;
    // 调用带回调函数参数的函数
    PerformOperation(value, callback);
    return 0;
}

Видно, что существенной разницы между двумя вышеописанными методами нет.

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

4. Функции-члены класса реализованы как нестатические функции.

  • Реализация 1
#include <iostream>
// ===========不变的模块,写死================
// 定义回调函数类型
typedef void (*CallbackFunction)(int);
// 带回调函数参数的函数
void PerformOperation(int value, CallbackFunction callback) {
    
    
    std::cout << "Performing operation with value: " << value << std::endl;
    // 执行某些操作...

    // 调用回调函数
    callback(value * 2);
}

// =========变化的模块,放到客户端进行实现========
// 回调类
class CallbackClass {
    
    
public:
    void Callback(int result) {
    
    
        std::cout << "Callback received result: " << result << std::endl;
        // 执行回调操作...
    }
};

int main() {
    
    
    int value = 5;

    // 创建回调类的对象
    CallbackClass callbackObj;
    // 将非静态成员函数作为回调函数
    CallbackFunction callback = [&](int result) {
    
    
        callbackObj.Callback(result);
    };
    // 调用带回调函数参数的函数
    PerformOperation(value, callback);

    return 0;
}
  • Реализация 2
#include <iostream>
// =========变化的模块,放到客户端进行实现========
class ProgramA {
    
    
 public:
  void FunA1() {
    
     printf("I'am ProgramA.FunA1() and be called..\n"); }

  void FunA2() {
    
     printf("I'am ProgramA.FunA2() and be called..\n"); }
};
// ===========不变的模块,写死================
class ProgramB {
    
    
 public:
  void FunB1(void (ProgramA::*callback)(), void *context) {
    
    
    printf("I'am ProgramB.FunB1() and be called..\n");
    ((ProgramA *)context->*callback)();
  }
};

int main(int argc, char **argv) {
    
    
  ProgramA PA;
  PA.FunA1();

  ProgramB PB;
  PB.FunB1(&ProgramA::FunA2, &PA);  // 此处都要加&
}

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

  • Реализация 3

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

#include <iostream>

// =========变化的模块,放到客户端进行实现========
class ProgramA {
    
    
 public:
  void FunA1() {
    
     printf("I'am ProgramA.FunA1() and be called..\n"); }

  void FunA2() {
    
     printf("I'am ProgramA.FunA2() and be called..\n"); }

  static void FunA2Wrapper(void *context) {
    
    
    printf("I'am ProgramA.FunA2Wrapper() and be called..\n");
    ((ProgramA *)context)->FunA2();  // 此处调用的FunA2()是context的函数, 不是this->FunA2()
  }
};

// ===========不变的模块,写死================
class ProgramB {
    
    
 public:
  void FunB2(void (*callback)(void *), void *context) {
    
    
    printf("I'am ProgramB.FunB2() and be called..\n");
    callback(context);
  }
};

int main(int argc, char **argv) {
    
    
  ProgramA PA;
  PA.FunA1();

  ProgramB PB;
  PB.FunB2(ProgramA::FunA2Wrapper, &PA);
}

Вышеописанный метод реализации обратных вызовов с помощью функции-обертки очень гибок, но все же недостаточно хорош.Например:
- 1) Есть дополнительная функция-обертка, которая не очень полезна.
- 2) Обертке также необходимо выполнить принудительное преобразование входящего указателя.
- 3) При вызове FunB2 необходимо указывать не только адрес функции-обертки, но и передавать адрес PA.

5. Использование std::function и std::bind.

#include <iostream>

#include <functional> // fucntion/bind
// =========变化的模块,放到客户端进行实现========
class ProgramA {
    
    
 public:
  void FunA1() {
    
     printf("I'am ProgramA.FunA1() and be called..\n"); }

  void FunA2() {
    
     printf("I'am ProgramA.FunA2() and be called..\n"); }

  static void FunA3() {
    
     printf("I'am ProgramA.FunA3() and be called..\n"); }
};

// ===========不变的模块,写死================
class ProgramB {
    
    
  typedef std::function<void ()> CallbackFun;
 public:
   void FunB1(CallbackFun callback) {
    
    
    printf("I'am ProgramB.FunB2() and be called..\n");
    callback();
  }
};

void normFun() {
    
     printf("I'am normFun() and be called..\n"); }

int main(int argc, char **argv) {
    
    
  ProgramA PA;
  PA.FunA1();

  printf("\n");
  ProgramB PB;
  PB.FunB1(normFun);
  printf("\n");
  PB.FunB1(ProgramA::FunA3);
  printf("\n");
  PB.FunB1(std::bind(&ProgramA::FunA2, &PA));
}

std::functionion поддерживает передачу адреса функции напрямую или указание его через std::bind.

Короче говоря, std::function определяет тип функции (ввод, вывод), а std::bind связывает конкретную функцию (конкретную функцию, которую нужно вызвать).

6. Реализация обратного вызова на C++

#include <functional>
#include <iostream>
// ===========不变的模块,写死================
class MyTest{
    
    
public:
    MyTest() = default;
    void doCalc(){
    
    
        //干其他事,完了
        // 执行回调
        if(myCallBack!= nullptr){
    
    
            myCallBack(1,2);
        }
    }

    using callback_t = std::function<void(const int &a, const int &b)>;

    // 注册回调
    void setCallBackHandler(const callback_t &cb){
    
    
        myCallBack = cb;
    }

private:
    // 定义回调
    callback_t myCallBack;
};

// =========变化的模块,放到客户端进行实现========
// 回调函数
void handleCallBack(const int &a,const int &b){
    
    
    std::cout << "this is from callback handleCallBack"<<std::endl;
}

int main(){
    
    

    MyTest t;

    // 回调函数
    auto f= [](const int &a,const int &b){
    
    
        std::cout << "this is from callback f"<<std::endl;
    };
    // 注册回调
    // 写法一
    t.setCallBackHandler(f);

    // 写法二
    t.setCallBackHandler([&f](auto &&a, auto &&b) {
    
    
        f(std::forward<decltype(a)>(a), std::forward<decltype(b)>(b));
    });

    // 写法三
    t.setCallBackHandler([](auto &&a, auto &&b) {
    
    
        handleCallBack(std::forward<decltype(a)>(a), std::forward<decltype(b)>(b));
    });

    t.doCalc();
}

7. Примеры применения

  • Отрывок из кодекса Гао Бо.

(1) Жестко запрограммированные модули

  • io_utils.h
#include <fstream>
#include <functional>
#include <utility>

class TxtIO {
    
    
   public:
    TxtIO(const std::string &file_path) : fin(file_path) {
    
    }

    /// 定义回调函数
    using IMUProcessFuncType = std::function<void(const IMU &)>;
    using OdomProcessFuncType = std::function<void(const Odom &)>;
    using GNSSProcessFuncType = std::function<void(const GNSS &)>;

    TxtIO &SetIMUProcessFunc(IMUProcessFuncType imu_proc) {
    
    
        imu_proc_ = std::move(imu_proc);
        return *this;
    }

    TxtIO &SetOdomProcessFunc(OdomProcessFuncType odom_proc) {
    
    
        odom_proc_ = std::move(odom_proc);
        return *this;
    }

    TxtIO &SetGNSSProcessFunc(GNSSProcessFuncType gnss_proc) {
    
    
        gnss_proc_ = std::move(gnss_proc);
        return *this;
    }

    // 遍历文件内容,调用回调函数
    void Go();

   private:
    std::ifstream fin;
    IMUProcessFuncType imu_proc_;
    OdomProcessFuncType odom_proc_;
    GNSSProcessFuncType gnss_proc_;
};
  • io_utils.cc
#include "io_utils.h"
#include <glog/logging.h>

void TxtIO::Go() {
    
    
    if (!fin) {
    
    
        LOG(ERROR) << "未能找到文件";
        return;
    }

    while (!fin.eof()) {
    
    
        std::string line;
        std::getline(fin, line);
        if (line.empty()) {
    
    
            continue;
        }

        if (line[0] == '#') {
    
    
            // 以#开头的是注释
            continue;
        }

        // load data from line
        std::stringstream ss;
        ss << line;
        std::string data_type;
        ss >> data_type;

        if (data_type == "IMU" && imu_proc_) {
    
    
            double time, gx, gy, gz, ax, ay, az;
            ss >> time >> gx >> gy >> gz >> ax >> ay >> az;
            // imu_proc_(IMU(time, Vec3d(gx, gy, gz) * math::kDEG2RAD, Vec3d(ax, ay, az)));
            imu_proc_(IMU(time, Vec3d(gx, gy, gz), Vec3d(ax, ay, az)));
        } else if (data_type == "ODOM" && odom_proc_) {
    
    
            double time, wl, wr;
            ss >> time >> wl >> wr;
            odom_proc_(Odom(time, wl, wr));
        } else if (data_type == "GNSS" && gnss_proc_) {
    
    
            double time, lat, lon, alt, heading;
            bool heading_valid;
            ss >> time >> lat >> lon >> alt >> heading >> heading_valid;
            gnss_proc_(GNSS(time, 4, Vec3d(lat, lon, alt), heading, heading_valid));
        }
    }

    LOG(INFO) << "done.";
}

Операция чтения файлов абстрагируется и записывается на смерть, а реализация конкретной обработки остается на усмотрение функции обратного вызова (клиента).

(2) Замена модулей

    /// 记录结果
    auto save_result = [](std::ofstream& fout, double timestamp, const Sophus::SO3d& R, const Vec3d& v,
                          const Vec3d& p) {
    
    
        auto save_vec3 = [](std::ofstream& fout, const Vec3d& v) {
    
     fout << v[0] << " " << v[1] << " " << v[2] << " "; };
        auto save_quat = [](std::ofstream& fout, const Quatd& q) {
    
    
            fout << q.w() << " " << q.x() << " " << q.y() << " " << q.z() << " ";
        };

        fout << std::setprecision(18) << timestamp << " " << std::setprecision(9);
        save_vec3(fout, p);
        save_quat(fout, R.unit_quaternion());
        save_vec3(fout, v);
        fout << std::endl;
    };

    std::ofstream fout("./data/ch3/state.txt");
    io.SetIMUProcessFunc([&imu_integ, &save_result, &fout, &ui](const sad::IMU& imu) {
    
    
          imu_integ.AddIMU(imu);
          save_result(fout, imu.timestamp_, imu_integ.GetR(), imu_integ.GetV(), imu_integ.GetP());
          if (ui) {
    
    
              ui->UpdateNavState(imu_integ.GetNavState());
              usleep(1e2);
          }
      }).Go();

Хорошо, это все еще очень практично.

Guess you like

Origin blog.csdn.net/fb_941219/article/details/130895404
C++