C++使用纯虚函数的接口封装以及接口的调用方法
受疫情的影响,本研狗最近一直在家养老,但是,实验室那边的项目突然压下来了,无奈,只好云办公呗。给的任务是对我之前完成的项目做一个封装,对外部提供接口使用。这**就涉及到我知识的盲区了呀,时间还催的挺紧,只好这两天边学边做,终于肝出来了一个初步的成果,做好了空的原型库定义,加上了要求的输入输出参数,封装成了接口。然后想着趁着热乎劲,赶紧把方法总结下来,遂发布了这篇文章,供大家参考,也给以后自己再写类似接口的时候当参考。
首先说一下为什么要用到纯虚函数,主要是因为要向用户提供接口,又不希望暴露太多的信息,于是在对外部发布的头文件里定义的方法都是纯虚的,也不包含私有方法和属性,而其具体实现以及成员变量由内部封装的库来实现。简单来说,就是要发布的外部文件定义抽象类当作父类,其内部子类继承父类,提供具体实现。
下面我整了一个很简单的小例子来说明这个是如何实现的,例子是调用opencv实现图片的水平翻转和灰度化操作。
小例子的代码均已上传到github:传送门
库的封装
首先,是对外发布的接口文件image_processing.h,如下所示。
// ------------------image_processing.h------------------
// 这个是对外发布的接口,这个文件里只包含对外提供的方法,不会包含私有方法和属性
#pragma once
#include <opencv2/opencv.hpp>
// 注:如果用到了.def来配置生成dll,则可以用下面被注释掉的代码
//#ifdef _EXPORTING
//#define CLASS_DECLSPEC __declspec(dllexport)
//#else
//#define CLASS_DECLSPEC __declspec(dllimport)
//#endif
#define CLASS_DECLSPEC __declspec(dllexport) // 表示这里要把类导出
// 基类
class Processing // 这个类是对外发布的,所有方法均是纯虚的,不会暴露实现
{
public:
// 提供接口框架的纯虚函数
virtual void imageProcessing(cv::Mat image) = 0; // 图像处理
virtual cv::Mat getTheResult() = 0; // 输出成员变量
// 这里最好不要出现私有成员变量,因为只是接口作用,不暴露实现,成员变量可以在具体实现头文件定义
};
可以看到,这个类里面只有一个成员函数,virtual的声明表示这个函数是纯虚的,还有后面的 = 0也要加上,告诉编译器这是个纯虚函数。
细心的童鞋可能就发现了,上面注释了一大段#define这些东西是什么鬼?解释一下,这个跟一个.def的配置文件有关,这个配置文件一般用于生成dll,也就是我们现在正在做的事情。这段注释意思是如果.def中define _EXPRORTING,就用__declspec(dllexport)来替代CLASS_DECLSPEC;如果没有define,就用__declspec(dllimport)来替代CLASS_DECLSPEC。
关于__declspec(dllexport)这个东西,它表示的是声明一个导出函数(或者导出类),是说这个函数(类)要从本DLL导出,表示要给别人用,一般用于生成dll。而__declspec(dllimport)是声明一个导入函数(或者导入类),是说这个函数(类)是从别的DLL导入,一般用于使用某个dll的exe中。
解释清楚了,再回过头看我为啥没有用这段注释的代码,因为这段代码是跟.def有关,我没有用.def,而是手动在头文件定义了这个就是输出。当然,在后面这个头文件给用户用的时候还是要再手动改回输入的。
然后,是具体实现的头文件,可以看到这里我对这两个派生类定义时用到了CLASS_DECLSPEC,结合前面的头文件,其实是用__declspec(dllimport)来定义的这个类,表示这两个类要导出。
// ----------------image_processing_impl.h----------------
// 实现文件,负责接口文件的具体实现,不对外发布
#pragma once
#include "image_processing.h"
// 派生类之灰度化操作
class CLASS_DECLSPEC ToGray : public Processing
{
public:
void imageProcessing(cv::Mat image);
cv::Mat getTheResult();
private:
cv::Mat result_image;
};
// 派生类之水平翻转操作
class CLASS_DECLSPEC Flip : public Processing
{
public:
void imageProcessing(cv::Mat image);
cv::Mat getTheResult();
private:
cv::Mat result_image;
};
然后是具体实现cpp文件。
// ----------------image_processing_impl.cpp----------------
// 实现文件,负责接口文件的具体实现,不对外发布
#include "image_processing_impl.h"
void ToGray::imageProcessing(cv::Mat image)
{
cv::cvtColor(image, result_image, CV_RGB2GRAY);
}
cv::Mat ToGray::getTheResult()
{
return result_image;
}
void Flip::imageProcessing(cv::Mat image)
{
cv::flip(image, result_image, 1);
}
cv::Mat Flip::getTheResult()
{
return result_image;
}
上述就是整个的封装,但是到这里还没有结束,因为用户接口头文件里定义的是抽象类,而抽象类并不能实例化,因此要提供用户创造对象的方式,还需对外发布一个用来创造实例的头文件,以及内部对应的cpp文件(当然内部也要有头文件)。
// -----------image_processing_factory.h-----------
// 创造实例头文件,对外发布
#pragma once
#include "image_processing.h"
class CLASS_DECLSPEC ToGrayFactory // 根据image_processing.h 定义这个是导入还是导出(外部导入,内部导出),下同理
{
public:
Processing* create(); // 提供一个创建对象的方法
};
class CLASS_DECLSPEC FlipFactory
{
public:
Processing* create(); // 提供一个创建对象的方法
};
// -----------image_processing_factory.cpp-----------
// 创造实例头文件
#include "image_processing_factory.h"
#include "image_processing_impl.h"
Processing* ToGrayFactory::create()
{
return new ToGray;
}
Processing* FlipFactory::create()
{
return new Flip;
}
好了,到这里封装工作就可以完成了,把这五个文件编译出dll和lib供外部调用即可。
封装库的使用
前面说了内部如何进行封装,下面说一下外部也就是用户这边如何使用。首先,必然需要前面文件编译出来的静态链接库.lib和动态链接库.dll,把这两个库文件拷贝到工程目录下。然后,还需要两个前面封装库用的两个头文件,作为接口文件。头文件 image_processing.h 定义了纯虚函数,内部有子类继承提供具体实现,供外部调用;头文件 image_processing_factory.h 提供把封装库的子类实例化来创造对象的方法。因此也要把这两个头文件拷贝至工程目录下。
总结一下,外部要调用库的话,需要编译出来的静态链接库.lib和动态链接库.dll,还有两个头文件 image_processing.h 和 image_processing_factory.h ,然后写个main函数调用即可。
但有一点要注意,外部调用的头文件image_processing.h要改动一下,把
#define CLASS_DECLSPEC __declspec(dllexport) // 表示这里要把类导出
改为
#define CLASS_DECLSPEC __declspec(dllimport) // 表示这里要把类导入
包含主函数的cpp如下所示。
// ----------demo.cpp----------
// 调用封装库实现对图片的操作
#include <opencv2/opencv.hpp>
#include "image_processing_factory.h"
int main()
{
ToGrayFactory factory_gray; // 用户接口
FlipFactory factory_flip; // 用户接口
Processing* processor_gray = factory_gray.create(); //创建灰度操作对象
Processing* processor_flip = factory_flip.create(); //创建翻转操作对象
cv::Mat orig_image = cv::imread("D:/test_image.jpg");
cv::imshow("orig_image", orig_image);
processor_gray->imageProcessing(orig_image);
cv::imshow("gray_image", processor_gray->getTheResult());
processor_flip->imageProcessing(orig_image);
cv::imshow("flip_image", processor_flip->getTheResult());
cv::waitKey(0);
return 0;
}
执行结果
来源:CSDN
作者:冀州少主
链接:https://blog.csdn.net/IcdKnight/article/details/104456560