首页 > 代码库 > 使用桥接模式时要注意的问题
使用桥接模式时要注意的问题
什么是桥接模式?举个例子:平时我们我们的USB(Universal Serial Bus(通用串行总线)),其实其设计的模式就是桥接模式。桥接模式的作用就是:将抽象部份与它的实现部份分离,使它们都可以独立地变化。也就是说无论USB插的是U盘,还是小风扇。都只是外部的独立变化,电脑内部是不受影响。不扯远了,这里不是主要说桥接模式的用法。下面看看使用桥接模式需要注意一个小小的问题。http://blog.csdn.net/yitouhan/article/details/41443221
#ifndef BRIDGE_H #define BRIDGE_H #include <stdlib.h> class CPU { public: CPU() { m_name = (char *)malloc(32); }; ~CPU() { free(m_name); }; private: char *m_name; }; class Computer { public: Computer(){}; ~Computer(){}; CPU &GetCPU1(){ return m_cpu; } CPU *GetCPU2(){ return &m_cpu; } private: CPU m_cpu; }; int main() { { Computer computer; { CPU cpu = computer.GetCPU1(); // 运行时报错 } } { Computer computer; { CPU *cpu = computer.GetCPU2(); } } return 0; } #endif
当运行上面的代码时,应该会产生一个错误,因为第一个例子是有误的。因为这涉及到浅拷贝(复制)和深拷贝。首先,我们知道如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数;而这个默认拷贝构造函数就是浅拷贝。
而在C++中,在用一个对象初始化另一个对象时,只复制了成员,并没有复制资源,使两个对象同时指向了同一资源的复制方式称为浅复制。也就是说CPU cpu = computer.GetCPU1();中的cpu跟computer中的m_cpu的m_name都是指同一个地址。所以当cpu生命周期结束时,cpu会调用析构函数;当computer生命周期结束时,m_cpu又会调用析构函数,从而导致析构函数会被调用再次,结合上面的例子,也就是m_name会被free了两次。因此会造成程序运行时错误。
当然,如果要解决这个问题,就是自定义拷贝构造函数(深拷贝)。
#ifndef BRIDGE_H #define BRIDGE_H #include <stdlib.h> #include <string.h> class CPU { static const int NAME_LENGTH = 32; public: CPU() { m_name = (char *)malloc(NAME_LENGTH); }; ~CPU() { free(m_name); }; CPU(CPU &cpu) { m_name = (char *)malloc(NAME_LENGTH); memcpy(m_name, cpu.GetName(), NAME_LENGTH); } char *GetName(){ return m_name; } private: char *m_name; }; class Computer { public: Computer(){}; ~Computer(){}; CPU &GetCPU1(){ return m_cpu; } CPU *GetCPU2(){ return &m_cpu; } private: CPU m_cpu; }; int main() { { Computer computer; { CPU cpu = computer.GetCPU1(); } } { Computer computer; { CPU *cpu = computer.GetCPU2(); } } return 0; } #endif
经过这样一修改,没错,代码是正确运行。但是实际应用,上述做法是不科学的。
很明显,实际上我是想获取Computer里面的CPU信息,而不是额外拷贝出一份信息出来。下面朝着这个方向去修改。
#ifndef BRIDGE_H #define BRIDGE_H #include <stdlib.h> #include <string.h> class CPU { static const int NAME_LENGTH = 32; public: CPU() { m_name = (char *)malloc(NAME_LENGTH); }; ~CPU() { free(m_name); }; CPU(CPU &cpu); // { // m_name = (char *)malloc(NAME_LENGTH); // memcpy(m_name, cpu.GetName(), NAME_LENGTH); // } // // char *GetName(){ return m_name; } private: char *m_name; }; class Computer { public: Computer(){}; ~Computer(){}; CPU &GetCPU1(){ return m_cpu; } CPU *GetCPU2(){ return &m_cpu; } private: CPU m_cpu; }; int main() { { Computer computer; { CPU cpu = computer.GetCPU1(); // 编译时报错 } } { Computer computer; { CPU *cpu = computer.GetCPU2(); } } return 0; } #endif
这段代码,只是注释了一段代码,其它并没有修改。但是只要编译这段的时候,编译就会无情报错。很明显是因为我只声明了拷贝构造函数,没有定义就直接引用它。这种做法可以避免写出这样的拷贝操作(CPU cpu = computer.GetCPU1();)。这种操作的不科学之处之前已经说明。当我们要调用CPU的函数时,我们只能computer.GetCPU1().XXX();这样操作。
有一行代码由始至终我都没有提及过的就是CPU *cpu = computer.GetCPU2();。这行代码在3个例子中都是正确的,都是能达到我的目的的。但是其也有容易出错的地方,那就是很容易被delete。
其实:
CPU &GetCPU1(){ return m_cpu; }
CPU *GetCPU2(){ return &m_cpu; }
没有那一种是绝对好,至于选择哪一种,可以根据自己的需求决定!这里只是说说使用它们时要注意的问题。
使用桥接模式时要注意的问题