C++中接口与实现分离技术 ,DLL导出类

60 篇文章 5 订阅
54 篇文章 1 订阅

(1)什么是接口和实现以及区别

请问类的实现是不是如下的解释:
用类定义一个对象,就像是int i; i就是int的实现一样,定义了就存在实际容量,那么定义的对象就是类的实现。
那么接口指的是什么?
请知道的高手好像'实现'的解释那样举个例子,说明一下!

一般地,我们称C++类声明中的函数原型为接口,它只是提供给了用户如何使用的具体细节,而隐藏了具体的代码;类似地,我们称类的成员函数的具体的代码为实现。如下:
class Test
{
public:
     void test();//此处的成员函数void test()仅仅告诉告诉用户调用它时传递几个参数,以及它返回什么值,什么作用等,并没有告诉用户test()函数到底怎么完成这些功能的,这就是接口;
};

void Test::test()
{...}//这里是实现,此处是test()函数具体的代码,用户无需关心它是怎么写的。

又如:
Test abc;//此处定义了一个Test类型的对象abcabc 一般我们称之为类的实例(不是实现)。一个没有定义对象的类,只是一个概念,而不是一个实体,即实际存在的东西。类的作用只有在定义了类的实例(也就是类对象)后才会体现出来(静态函数和成员例外)。仍以int i;为例,int就好比是类(class),而i好比是类的实例。

2)接口和实现分离技术

在用C++写要导出类的库时,我们经常只想暴露接口,而隐藏类的实现细节。也就是说我们提供的头文件里只提供要暴露的公共成员函数的声明,类的其他所有信息都不会在这个头文件里面显示出来。这个时候就要用到接口与实现分离的技术。

  下面用一个最简单的例子来说明。

   类ClxExp是我们要导出的类,其中有一个私有成员变量是ClxTest类的对象,各个文件内容如下:

  lxTest.h文件内容:

class ClxTest 
{
public:
    ClxTest();
    virtual ~ClxTest();
 
    void DoSomething();
};

  lxTest.cpp文件内容:

#include "lxTest.h"

#include <iostream>
using namespace std;

ClxTest::ClxTest()
{
}

ClxTest::~ClxTest()
{
}

void ClxTest::DoSomething()
{
    cout << "Do something in class ClxTest!" << endl;
}

///

   lxExp.h文件内容:

#include "lxTest.h"

class ClxExp 
{
public:
    ClxExp();
    virtual ~ClxExp();

    void DoSomething();

private:
    ClxTest m_lxTest;

    void lxTest();
};

lxExp.cpp文件内容:

#include "lxExp.h"

ClxExp::ClxExp()
{
}

ClxExp::~ClxExp()
{
}

// 
其实该方法在这里并没有必要,这样只是为了说明调用关系
void ClxExp::lxTest()
{
    m_lxTest.DoSomething();
}

void ClxExp::DoSomething()
{
    lxTest();
}

  为了让用户能使用我们的类ClxExp,我们必须提供lxExp.h文件,这样类ClxExp的私有成员也暴露给用户了。而且,仅仅提供lxExp.h文件是不够的,因为lxExp.h文件include了lxTest.h文件,在这种情况下,我们还要提供lxTest.h文件。那样ClxExp类的实现细节就全暴露给用户了。另外,当我们对类ClxTest做了修改(如添加或删除一些成员变量或方法)时,我们还要给用户更新lxTest.h文件,而这个文件是跟接口无关的。如果类ClxExp里面有很多像m_lxTest那样的对象的话,我们就要给用户提供N个像lxTest.h那样的头文件,而且其中任何一个类有改动,我们都要给用户更新头文件。还有一点就是用户在这种情况下必须进行重新编译!上面是非常小的一个例子,重新编译的时间可以忽略不计。但是,如果类ClxExp被用户大量使用的话,那么在一个大项目中,重新编译的时候我们就有时间可以去喝杯咖啡什么的了。当然上面的种种情况不是我们想看到的!你也可以想像一下用户在自己程序不用改动的情况下要不停的更新头文件和编译时,他们心里会骂些什么。其实对用户来说,他们只关心类ClxExp的接口DoSomething()方法。那我们怎么才能只暴露类ClxExp的DoSomething()方法而不又产生上面所说的那些问题呢?答案就是--接口与实现的分离。我可以让类ClxExp定义接口,而把实现放在另外一个类里面。下面是具体的方法:

  首先,添加一个实现类ClxImplement来实现ClxExp的所有功能。注意:类ClxImplement有着跟类ClxExp一样的公有成员函数,因为他们的接口要完全一致

  lxImplement.h文件内容:

#include "lxTest.h"

class ClxImplement 
{
public:
    ClxImplement();
    ~ClxImplement();

    void DoSomething();

private:
    ClxTest m_lxTest;

    void lxTest();
};

  lxImplement.cpp文件内容:

#include "lxImplement.h"

ClxImplement::ClxImplement()
{
}

ClxImplement::~ClxImplement()
{
}

void ClxImplement::lxTest()
{
    m_lxTest.DoSomething();
}

void ClxImplement::DoSomething()
{
    lxTest();
}

然后,修改类ClxExp。

  修改后的lxExp.h文件内容:

//  前置声明
class ClxImplement;

class ClxExp 
{
public:
    ClxExp();
    virtual ~ClxExp();

 void DoSomething();

private:
    //  声明一个类ClxImplement的指针,不需要知道类ClxImplement的定义

    ClxImplement *m_pImpl;
};

        修改后的lxExp.cpp文件内容:

//  在这里包含类ClxImplement的定义头文件
#include "lxImplement.h"

ClxExp::ClxExp()
{
    m_pImpl = new ClxImplement;
}

ClxExp::~ClxExp()
{
    if (m_pImpl)
        delete m_pImpl;
}

void ClxExp::DoSomething()
{
    m_pImpl->DoSomething();
}

  通过上面的方法就实现了类ClxExp的接口与实现的分离。请注意两个文件中的注释。类ClxExp里面声明的只是接口而已,而真正的实现细节被隐藏到了类ClxImplement里面。为了能在类ClxExp中使用类ClxImplement而不include头文件lxImplement.h,就必须有前置声明class ClxImplement,而且只能使用指向类ClxImplement对象的指针,否则就不能通过编译。在发布库文件的时候,我们只需给用户提供一个头文件lxExp.h就行了,不会暴露类ClxExp的任何实现细节。而且我们对类ClxTest的任何改动,都不需要再给用户更新头文件(当然,库文件是要更新的,但是这种情况下用户也不用重新编译!)。这样做还有一个好处就是,可以在分析阶段由系统分析员或者高级程序员来先把类的接口定义好,甚至可以把接口代码写好(例如上面修改后的lxExp.h文件和lxExp.cpp文件),而把类的具体实现交给其他程序员开发。

上文还没有考虑到类与类之间的继承关系。下面我们就来具体的谈谈这个方面。

  还是以上面提到的那篇文章中的例子来说明。

  执行类:

  lxImplement.h文件内容:

#include "lxTest.h"

class ClxImplement 
{
public:
    ClxImplement();
    ~ClxImplement();

    void DoSomething();

private:
    ClxTest m_lxTest;

    void lxTest();
};

  lxImplement.cpp文件内容:

#include "lxImplement.h"

ClxImplement::ClxImplement()
{
}

ClxImplement::~ClxImplement()
{
}

void ClxImplement::lxTest()
{
    m_lxTest.DoSomething();
}

void ClxImplement::DoSomething()
{
    lxTest();
}

  接口类:

  lxExp.h文件内容:

//  前置声明
class ClxImplement;

class ClxExp 
{
public:
    ClxExp();
    virtual ~ClxExp();

 void DoSomething();

private:
    //  声明一个类ClxImplement的指针,不需要知道类ClxImplement的定义

    ClxImplement *m_pImpl;
};

  lxExp.cpp文件内容:

//  在这里包含类ClxImplement的定义头文件
#include "lxImplement.h"

ClxExp::ClxExp()
{
    m_pImpl = new ClxImplement;
}

ClxExp::~ClxExp()
{
    if (m_pImpl)
        delete m_pImpl;
}

void ClxExp::DoSomething()
{
    m_pImpl->DoSomething();
}

但是,如果类ClxExp是另一个类的子类,而在类ClxExp中要调用基类的方法,那上面的方案就不行了。比如说,类ClxExp的基类是下面的样子:

class ClxInF
{
public:
    ClxInF();
    virtual ~ClxInF();

    bool InitSet();

    virtual void DoSomething();
};

  相应的类ClxExp的声明变成了如下的形式:

class ClxExp : public ClxInF
{
public:
    ClxExp();
    virtual ~ClxExp();

    void DoSomething();

private:
    ClxImplement *m_pImpl;
};

  现在,假设我们必须在类ClxExp的DoSomething()方法中根据InitSet()的返回值来确定是否执行操作。最简单的实现方法是把类ClxExp的DoSomething()方法改成下面的样子:

void ClxExp::DoSomething()
{
    if (InitSet())
        m_pImpl->DoSomething();
}

  可是如果这样的话,接口与实现就没有彻底的分离,因为实现细节被暴露到了接口类中。为了避免这种情况发生,我们就必须把对基类ClxInF的方法InitSet()调用放到执行类ClxImplement当中。可是怎么在执行类ClxImplement当中调用接口类ClxExp的基类ClxInF的方法呢?其实很简单,因为类ClxExp是类ClxInF的子类,那么它也就继承了类ClxInF的方法,只要把类ClxExp的this指针传给类ClxImplement,就可以通过这个指针来调用类ClxExp的方法,当然也可以调用类ClxExp从基类ClxInF继承来的方法。下面是修改后的代码:

  lxImplement.h文件内容:

#include "lxTest.h"
//
包含声明类ClxExp的头文件
#include "lxExp.h"

class ClxImplement 
{
public:
    // 
构造函数,传入类的ClxExp的指针
    ClxImplement(ClxExp *plxExp);
    ~ClxImplement();

    void DoSomething();

private:
    ClxTest m_lxTest;
    // 
定义一个类ClxExp的指针,可以通过该指针调用类ClxExp从基类继承下来的方法
    ClxExp *m_plxExp;

    void lxTest();
};

  lxImplement.cpp文件内容:

#include "lxImplement.h"

ClxImplement::ClxImplement(ClxExp *plxExp)
{
    m_plxExp = plxExp;
}

ClxImplement::~ClxImplement()
{
}

void ClxImplement::lxTest()
{
    m_lxTest.DoSomething();
}

void ClxImplement::DoSomething()
{
    if (m_plxExp->InitSet())
        lxTest();
}

  对于类ClxExp来说,只要修改一下它的构造函数就行了,其他都不用修改。

ClxExp::ClxExp()
{
    m_pImpl = new ClxImplement(this);
}

  这样,我们就解决了前面所提到的问题。

  当然,也许有人会说,让类ClxImplement也从类ClxInF继承不是更简单吗?那样就可以在类ClxImplement中直接调用类ClxInF的方法,也不用添加什么代码。可是我们知道公有继承是的子类与基类是IS-A的关系。也就是说子类是一种基类,就像说轿车是一种汽车一样。可是,在我们例子中,类ClxImplement只是类ClxExp的一个执行类而已,跟类ClxExp的基类ClxInF没有一点儿关系,更不要说是一种ClxInF了。所以不能让类ClxImplement从类ClxInF继承。

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值