C++桥接模式的研究

C++桥接模式的研究

注:本实例在Slackware Linux下使用g++4.5测试通过,在Windows 7下使用Visual C++ .NET 2008测试通过。

C++的设计模式中有一个叫做桥接模式。我最初是在Code Compelete上看到的,但是只是一句话带过,并没有具体说明如何实现(现在看来,不是它没讲,而是那个时候我没有研究过编译和链接……)。

在讲C++的桥接模式前,我先讲一下C++编译器的一个问题。

这个问题是C++编译时不会将对象的类型存入目标代码,导致了C++对对象的保护仅仅限于编译时保护,而不是链接时和运行时的保护。我在这里讲估计也没人会理解,那么我就用实际的例子来看一下吧。

unsafe.h:

#ifndef UNSAFE_H
#define UNSAFE_H

class UnSafePoint {
public:
    UnSafePoint(int x = 0, int y = 0);
    int GetX(void);
    int GetY(void);

private:
    int m_X;
    int m_Y;
};

#endif //UNSAFE_H

unsafe.cpp:

#include "unsafe.h"

UnSafePoint::UnSafePoint(int x, int y){
    m_X = x;
    m_Y = y;
}

int UnSafePoint::GetX(void){
    return m_X;
}

int UnSafePoint::GetY(void){
    return m_Y;
}

上面就是我们常常会写的代码了,你会觉得没有什么问题啊。但是,应该有很多人试过一件事:用指针去操作对象,这样可以间接地改变对象的私有成员。这个问题是C和C++里常见的问题了,而且应该是人尽共知的吧。所以我暂且不谈指针的问题。

我要谈的是目标代码的问题。

你会认为保护代码的最有力手段就是把那个unsafe.cpp编译成目标代码,将它的目标代码文件和unsafe.h作为一个函数库发布。

但是,我告诉你,这还是无法彻底保护私有数据的(在不使用指针的情况下)!

好的,我来做一个破坏类的访问权限的示例。

首先我们先把我们的unsafe.cpp编译成一个二进制库,为了方便,我就不生成一个完整的静态链接库了,而是一个目标文件,反正作用是一样的。

命令行如下:
    g++ unsafe.cpp -c -o unsafe.o -std=c++98

而后我们来测试一下这个类库。新建一个目录test,把unsafe.h和unsafe.o复制到这个目录中。然后让我们修改一下啊unsafe.h。修改后的代码如下:

unsafe.h:

#ifndef UNSAFE_H
#define UNSAFE_H

class UnSafePoint {
public:
    UnSafePoint(int x = 0, int y = 0);
    int GetX(void);
    int GetY(void);

public:
    int m_X;
    int m_Y;
};

#endif //UNSAFE_H

我们可以看出来,其实我是把头文件中数据域的private改成了public。好吧,我想大部分人都没有尝试过做过这种事吧……

接着就是我们的测试代码了。

main.cpp:

#include <iostream>
#include <cstdlib>
#include "unsafe.h"

int main(){
    using std::cout;
    using std::endl;

    UnSafePoint point(10, 10);

    cout << "Before change the value: " << point.GetX() << ' ' << point.GetY() << endl;

    point.m_X = 0;
    point.m_Y = 0;

    cout << "After change the value: " << point.GetX() << ' ' << point.GetY() << endl;

    return EXIT_SUCCESS;
}

你会发现我们在直接存取point对象的成员变量……好吧,很多人大概会认为这个程序无法编译通过吧,就让我们便一下吧……

命令行如下:
    g++ main.cpp unsafe.o -std=c++98 -o test

注意我们是直接把已经编译好的二进制库链接进去的……

好吧,说实话,我尝试的时候也是忐忑的,但是,上天没有辜负我,这个程序编译链接通过了……

至于输出,如下所示:
Before change the value: 10 10
After change the value: 0 0


很多人看到了估计会目瞪口呆,但是,事实如此……

那么为什么如此呢?这个就牵涉到了编译原理和链接装载的原理了……我真的没法一句话解释,我只能说生成二进制码时什么public,private,protected之类的都会被丢掉,其实如果不开启RTTI的话,甚至连类本身的类型信息也是不会保留的……

到这里,那些坚信编译类库可以保护私有数据的人估计可以死心了……

事到如此,我们该如何是好呢。现在看来一个唯一的解决(我知道的……)就是使用桥接设计模式了。

什么叫桥接模式呢?我不想说一大段话,还是先上代码……

safe.h:

#ifndef SAFE_H
#define SAFE_H

struct PointType;

class SafePoint {
public:
    SafePoint(int x = 0, int y = 0);
    int GetX(void);
    int GetY(void);

private:
   PointType* m_pData;
};

#endif //SAFE_H

safe.cpp:

#include "safe.h"

struct PointType {
    int m_X;
    int m_Y;
};

SafePoint::SafePoint(int x, int y){
    m_pData = new PointType;
    m_pData -> m_X = x;
    m_pData -> m_Y = y;
}

int SafePoint::GetX(void){
    return m_pData -> m_X;
}

int SafePoint::GetY(void){
    return m_pData -> m_Y;
}

你会发现我使用了一个类型为PointType*的指针来存储私有数据,然后所有有关该类型的数据和操作都存在了safe.cpp中了。

所谓桥接模式,其实就是使用了一座桥,这边的桥就是那个类型为PointType*的指针,利用这个“桥”,我们可以间接地操作私有数据。由于实现和接口完全分离了,所以也就更好地保护了私有数据。

为什么呢?因为即使你把private改成public你也无法合理操作m_pData指向的对象(除非使用指针,但是你甚至会不知道你自己在干什么……因为你对这个类型一无所知)。这样我们就更好地分离了接口和实现。

接下来看我们如何发布和使用这个类库:

首先编译这个类库:
    g++ safe.cpp -c -o safe.o -std=c++98

然后建立一个叫做test的目录,复制safe.h和safe.o放在test下。接下来我们编写我们的测试程序,顺便看一下如何使用这个类库:

测试代码
main.cpp:

#include <cstdlib>
#include <iostream>
#include "safe.h"

int main(){
    using std::cout;
    using std::endl;

    SafePoint point(10, 10);

    cout << "The value is: " << point.GetX() << ' ' << point.GetY()  << endl;

    return EXIT_SUCCESS;
}

我们来使用我们的类库编译我们的程序:
g++ main.cpp safe.o -std=c++98 -o test

运行结果就是:
The value is: 10 10

好了,我们就这样写了一个简单的利用桥接模式实现的类库。

可能有人会无法理解为什么可以这样做,还是一句话,去看一下编译原理吧,你会受益匪浅的。

不过这种方法的缺点就是会降低运行效率,你也无法使用内联函数来直接访问私有成员了……

有利有弊,具体利大于弊还是弊大于利,仁者见仁,智者见智吧。

不过无论你使用了什么方法,你最重要祈祷的是用户不要乱用指针……
 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值