PIMPL C++隐藏实现细节技术
PIMPL全称为Pointer to Implementation,其在cppreference上的解释为:
“Pointer to implementation” or “pImpl” is a C++ programming technique that removes implementation details of a class from its object representation by placing them in a separate class, accessed through an opaque pointer
如下是本人最近正在实现的一个KalmanFilter封装项目部分tree:
│ └── Interface│ ├── Kalmer.h│ ├── KalmImplGaGa│ │ ├── KalmanImplKf.h│ │ └── KalmanImplUkf.h│ └── KalmImpl.h├── LICENSE├── main.cpp├── readme.md└── src └── Kalm.cpp#ifndef __WORKSPACE_KALMANFILTER_INC_INTERFACE_KALMER_H_#define __WORKSPACE_KALMANFILTER_INC_INTERFACE_KALMER_H_#include "Eigen/Core"#include "Define/basic_type.h"#include "Define/config.h"#include <memory>class KalmImpl;class KalmImplKf;class KalmImplUkf;class Kalmer{private: /* data */public: Kalmer(/* args */); ~Kalmer(); RC init(cf_ic &&); RC predict(); RC update();private: std::shared_ptr<KalmImpl> sp_kalman_impl_;};
#endif // __WORKSPACE_KALMANFILTER_INC_INTERFACE_KALMER_H_Kalmer类在此项目中作为最外层接口,当然,也可以将类名前加大写字母I,表示接口(Interface),在此不做赘述。
通过上述代码,可以发现Kalmer类包含一个智能指针指向**KalmImpl**类,其为Kalmer的实现类。
你以为Kalmer的成员函数init predict update三个函数中包含具体实现吗?你以为这三个函数包含了矩阵的眼花缭乱的操作吗?
其实不是这样的。请看下面代码:
#include "Interface/Kalmer.h"#include "Interface/KalmImpl.h"#include "Interface/KalmImplGaGa/KalmanImplKf.h"#include "Interface/KalmImplGaGa/KalmanImplUkf.h"Kalmer::Kalmer(/* args */){}Kalmer::~Kalmer(){}RC Kalmer::init(cf_ic && _init_config){ if (_init_config.kal_type_ == kalman_types::NORMAL_KF_) { sp_kalman_impl_ = std::make_shared<KalmImplKf>(); return RC::RETURN_OK; } if (_init_config.kal_type_ == kalman_types::UNSCENTED_KF_) { sp_kalman_impl_ = std::make_shared<KalmImplUkf>(); return RC::RETURN_OK; }
printf("%s unknown error\n", __FUNCTION__); return RC::RETURN_ERROR;}RC Kalmer::predict(){ return sp_kalman_impl_->predict();}
RC Kalmer::update(){ return sp_kalman_impl_->update();}可以发现Kalmer类的实现cpp中,其三个public函数并未有实质性算法代码
-
init用于通过config配置文件初始化其算法指针,至于为什么
sp_kalman_impl明明是KalmImpl类,为何可以被KalmImplKf和KalmImplUkf实例化,这涉及到继承的问题,后面贴上两个子类的实现cpp文件,大家就一目了然了。 -
predict调用成员指针的predict方法
-
update调用成员指针的update方法
同样可以发现:真正的核心算法并没有在这个cpp文件中实现,当然如果你想的话,也可以在这个cpp文件中实现。而后我们通过CMakelist编译出library,再配上光秃秃的Kalmer.h就可以提供给算法的使用者了,这种情况下可以最小化暴露我们算法的实现。
当前这个工程不能很完美的掩盖算法的实现细节,因为它暴露了3个public函数给使用者,可是卡尔曼滤波至少得有2个方法。
通常一个算法接口应当只包含一个public方法,其名称通常为run 或 process,当然如果你想,也可以命名为gagaga(只要你的下游使用者能明白就行)。
再看看KalmImpl和他的子类KalmanImplKf的实现吧
#ifndef __WORKSPACE_KALMANFILTER_INC_INTERFACE_KALMIMPL_H_#define __WORKSPACE_KALMANFILTER_INC_INTERFACE_KALMIMPL_H_#include "Interface/Kalmer.h"class KalmImpl{private: /* data */public: KalmImpl(cf_ic &&_init_config) : sp_state_(std::make_shared<kalman_matrix_state_t>(_init_config)), sp_confg_(std::make_shared<matrix_size_config__t>(_init_config)){}; virtual ~KalmImpl() = default; virtual RC predict() = 0; virtual RC update() = 0; RC set_measu(Eigen::VectorXf &&_measu) { // TODO: check size if(_measu.size() != sp_confg_->msu_size_) { printf("KalmImpl::set_measu check error\n"); return RC::RETURN_ERROR; } sp_state_->v_measu_ = _measu; return RC::RETURN_OK; }; RC set_P(Eigen::MatrixXf &&_P){ sp_state_->m_P_ = _P; } RC set_Q(Eigen::MatrixXf &&_Q) { sp_state_->m_Q_ = _Q; } RC set_R(Eigen::MatrixXf &&_R) { sp_state_->m_R_ = _R; } RC set_F(Eigen::MatrixXf &&_F) { sp_state_->m_F_ = _F; }
protected: using kalman_matrix_state_t = km_s; using matrix_size_config__t = cf_ic; std::shared_ptr<kalman_matrix_state_t> sp_state_; std::shared_ptr<matrix_size_config__t> sp_confg_;};
#endif // __WORKSPACE_KALMANFILTER_INC_INTERFACE_KALMIMPL_H_可以看到父类KalmImpl.h中将predict和update两个函数声明为纯虚函数,其目的时让子类KalmImplKf和KalmImplUkf去继承和实现。
其被直接实现的几个函数可以被子类所用,因为卡尔曼滤波和无迹卡尔曼滤波本质时一样的,都需要去设置协方差矩阵、噪音矩阵、转换矩阵等。关于卡尔曼滤波的原理将会在后续的博文中详细阐述。
#ifndef __WORKSPACE_KALMANFILTER_INC_INTERFACE_KALMIMPLGAGA_KALMANIMPLKF_H_#define __WORKSPACE_KALMANFILTER_INC_INTERFACE_KALMIMPLGAGA_KALMANIMPLKF_H_#include "../KalmImpl.h"
class KalmImplKf : public KalmImpl{public: KalmImplKf(cf_ic &&_init_config) : KalmImpl(std::move(_init_config)) {} virtual RC predict() { auto s = sp_state_; s->v_state_ = s->m_F_ * s->v_state_; s->m_P_ = s->m_F_ * s->v_state_ * s->m_F_.transpose() + s->m_Q_; } virtual RC update() { auto s = sp_state_; s->m_K_ = s->m_P_ * s->m_H_.transpose() * ((s->m_H_ * s->m_P_ * s->m_H_.transpose() + s->m_R_).inverse()); s->v_state_ = s->v_state_ + s->m_K_ * (s->v_measu_ - s->m_H_ * s->v_state_); s->m_P_ = s->m_P_ - s->m_K_ * s->m_H_ * s->m_P_; } virtual ~KalmImplKf() override{}};#endif // __WORKSPACE_KALMANFILTER_INC_INTERFACE_KALMIMPLGAGA_KALMANIMPLKF_H_上述代码为卡尔曼滤波的实现,可以看到直到这里,算法才真正的被实现。加粗的五个公式为卡尔曼滤波的矩阵运算公式,其矩阵形式引用了Eigen。
至于UKF的实现我还没有写完,具体项目地址。
画外音:本人硕士写的YOLOV3的论文是怎么跟知乎的身体健康类问题挂上边的,具体地址。