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类,为何可以被KalmImplKfKalmImplUkf实例化,这涉及到继承的问题,后面贴上两个子类的实现cpp文件,大家就一目了然了。
  • predict调用成员指针的predict方法
  • update调用成员指针的update方法

同样可以发现:真正的核心算法并没有在这个cpp文件中实现,当然如果你想的话,也可以在这个cpp文件中实现。而后我们通过CMakelist编译出library,再配上光秃秃的Kalmer.h就可以提供给算法的使用者了,这种情况下可以最小化暴露我们算法的实现。

当前这个工程不能很完美的掩盖算法的实现细节,因为它暴露了3个public函数给使用者,可是卡尔曼滤波至少得有2个方法。

通常一个算法接口应当只包含一个public方法,其名称通常为runprocess,当然如果你想,也可以命名为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的论文是怎么跟知乎的身体健康类问题挂上边的,具体地址


已发布

分类

来自

标签:

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注