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
好了,我们就这样写了一个简单的利用桥接模式实现的类库。
可能有人会无法理解为什么可以这样做,还是一句话,去看一下编译原理吧,你会受益匪浅的。
不过这种方法的缺点就是会降低运行效率,你也无法使用内联函数来直接访问私有成员了……
有利有弊,具体利大于弊还是弊大于利,仁者见仁,智者见智吧。
不过无论你使用了什么方法,你最重要祈祷的是用户不要乱用指针……
C++桥接模式的研究
最新推荐文章于 2024-08-30 16:00:44 发布