首页 > 代码库 > 使用桥接模式时要注意的问题

使用桥接模式时要注意的问题

什么是桥接模式?举个例子:平时我们我们的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; }

没有那一种是绝对好,至于选择哪一种,可以根据自己的需求决定!这里只是说说使用它们时要注意的问题。



使用桥接模式时要注意的问题