C++中如何正确的使用接口类

前一篇文章中讲过C++中如何正确的定义接口类,那定义好的接口类如何正确使用?本篇将细细说说。

提供接口与实现

首先,声明一个接口:

// circle.h
// 圆的接口类
class Circle {
public:
	virtual ~Circle() {};

	// 接口方法:面积
	virtual double area() = 0;
};

通过继承的方式实现这个接口:

// circle_impl.h
#include "circle.h"

// 圆的具体实现类
class CircleImpl : public Circle {

private:
	double radius;
public:
	CircleImpl(double radius);
	double area() override;
};
// circle_impl.cpp
#include <cmath>
#include "circle_impl.h"

inline double pi() {
	return std::atan(1) * 4;
};

CircleImpl::CircleImpl(double _radius) : radius(_radius) {
};

double CircleImpl::area() {
	return pi() * radius * radius;
};

最后,通过管理类创建接口派生类的实例,或者销毁接口派生类的实例:

// circle_manager.h
#include "circle.h"

// 圆的创建工厂类
class CircleManager {
public:
    Circle* create(double radius);     // 创建circle实例
    void destroy(Circle* circlePtr);   // 销毁circle实例
};
// circle_manager.cpp
#include "circle_manager.h"
#include "circle_impl.h"

Circle* CircleManager::create(double radius) {
    Circle* circlePtr = new CircleImpl(radius);

    return circlePtr;
};

void CircleManager::destroy(Circle* circlePtr) {
    delete circlePtr;
}; 

代码目录结构:

proj-+
     |-inc-+
     |     |-circle.h
     |     |-circle_manager.h
     |
     |-src-+
           |-circle_impl.h
           |-circle_impl.cpp
           |-circle_manager.cpp

其中inc目录用于存放Circle接口类和Circle管理类的声明,src目录中存放Circle实现类CircleImpl的声明和定义、Circle管理类CircleManager的定义。

然后,可以将以上代码编译成静态库circle.lib,并和inc目录中的头文件一起提供给外部调用:

如何使用静态库?

外部使用者编译时,需要做如下配置:

1). 把inc目录添加到“附加包含目录”中。

2). “附加依赖项”中添加circle.lib

3). 把circle.lib所在目录的路径添加到附加库目录中。

扫描二维码关注公众号,回复: 1868501 查看本文章

外部使用者的代码如下:

// main.cpp
#include <iostream>
#include "circle_manager.h"
#include "circle.h"

int main() {
	CircleManager cm;
	Circle* circlePtr = cf.create(3);

	cout << circlePtr->area() <<endl;

	system("pause");

	return 0;
}

以上代码只提供给外部circle的接口,circle的实现完全被隐藏了起来,外部将无从知晓,外部使用者只能通过circle管理类生成circle的派生类的实例。外部使用者得到circle派生类的实例后,除了能调用接口暴露的方法area()外,其它什么也做不了,这样就完全达到了使用接口的最终目标。

如何编译成动态库?

首先,添加一个新的头文件:

// dll_export.h

// if windows .dll
#ifdef _WINDLL

#ifdef DLL_API_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif

// else if Linux or macOS .so
#else

#define DLL_API

#endif

添加此头文件后,代码可以在windows、Linux下都可编译生成动态库,只需在编译时设置不同参数就行了。

windows: /D "DLL_API_EXPORTS" /D "_WINDLL"

Linux: 不用配置额外参数

circle.h和circle_manager.h也要做相应改动:

// circle.h
#pragma once

#include "dll_export.h"

// 圆的接口类
class DLL_API Circle {
public:
       virtual ~Circle() {};
        // 接口方法:面积
        virtual double area() = 0;
};
// circle_manager.h
#pragma once

#include "circle.h"
#include "dll_export.h"

 // 圆的创建工厂类
class DLL_API CircleManager {
public:
	Circle* create(double radius);

	void destroy(Circle* circlePtr);
};

编译完成后将生成”circle.lib“和”circle.dll“文件:

proj-+
     |-inc-+
     |     |-circle.h
     |     |-circle_manager.h
     |
     |-src-+
     |     |-circle_impl.h
     |     |-circle_impl.cpp
     |     |-circle_manager.cpp
     |
     |-bin-+
           |-circle.lib
           |-circlr.dll

如何使用动态库?

外部使用者编译时,需要做如下配置:

1). 代码中添加#pragma comment(lib,"circle.lib"), 这里是circle.lib,不是circle.dll。

2). 把inc目录添加到“附加包含目录”中。

3). “附加依赖项”中添加circle.lib,这里也是circle.lib,不是circle.dll。

4). 把bin目录所在路径添加到”附加库目录“中。


新的外部使用者的代码如下:

#include <iostream>
#include "circle_manager.h"
#include "circle.h"

#pragma comment(lib,"circle.lib")

int main() {
	CircleManager cf;
	Circle* circlePtr = cf.create(3);
	cout << circlePtr->area() << endl;
	cf.destroy(circlePtr);

	system("pause");

	return 0;
}

总结

这里有几点需要说明一下:

1、为什么CircleManager类即在提供创建实例的方法又要提供销毁实例的方法?

由于编译器的实现方式不同,dll的堆空间可能跟调用方的堆空间不同,它可能是由dll自己单独管理的,所以从dll中创建的实例,最好还是在dll中销毁。

2、对动态库的调用本文是通过隐式调用的方式完成的,对动态库的调用也可以使用显式调用的方式,但由于windows和Linux在使用显式调用时的API是不同的,不好提供统一的代码,所以本文没有举例,以后有机会再单独行文介绍。


参考文档

HowTo: Export C++ classes from a DLL

Exporting C++ classes from a DLL

DLLs in Visual C++

Microsoft Visual Studio .NET 2003 Warning C4251

Exporting classes containing std:: objects (vector, map, etc) from a dll

猜你喜欢

转载自blog.csdn.net/netyeaxi/article/details/80887646