C++使用纯虚函数的接口封装以及接口的调用方法

依然范特西╮ 提交于 2020-02-24 05:04:48

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;
}

执行结果

在这里插入图片描述

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!