天天看点

单件模式的几种实现方法单件模式的几种实现方法

单件模式的几种实现方法

       在C++里,有时候,我们只想一个类只实例化一次,因为有些类没必要实例化多次,我们也不想,而且会浪费空间,更重要的是达不到我们想要的那种效果。也就是单件模式所能实现的一些效果,可以代替全局变量的一个模式。

比如有个专门负责实例化对象的工厂类,这里之所以要把实例化的工作专门放到工厂类中,是因为这样可以很好的控制对象的生命,而且可以更好的控制抽象类与具体类的关系,我们可以在工厂类中动一些手脚,而达到一种效果,就是通过这个工厂类产生的实例,只需要创建即可,无需手动删除,如果不强制删除,则会自动删除,但是……这个不是这里的主题,C++和JAVA、C#不一样,它没有垃圾回收机制,很多东西都需要手动,比如凡是在堆内存分配的空间,即new出来的实例,必须得delete删除之,否则会内存泄漏,当然,用智能指针是可以办到自动delete,就是把实例创建后的指针放进一个另一个对象里面保存,而那个对象保证不是new出来的,然后进行平常的操作,比如std::auto_ptr<int> pInt(new int(10));当程序运行完毕后,会自动delete删除掉。

回到我们的主题,如何实现一个类只实例化一次,首先来看一下Tinking in C++的做法:

#include<iostream>

class Factory

{

private:

    int value;

    //不允许自行创建

    Factory(){cout<< "Factory" <<endl;value = 0;};

    //不允许赋值操作

void operator=(const Factory&);

//不允许复制

    Factory(const Factory&);

    ~Factory(){cout<< "~Factory" <<endl;};

public:

    //通过这个函数进行实例化

    static Factory& Instance()

    {

       static Factory factory;

       return factory;

    }

    void SetValue(int i )

    {

       value= i;

    }

    int GetValue()

    {

       return value;

    }

};

int main()

{

    Factory&factory1 = Factory::Instance();

    Factory&factory2 = Factory::Instance();

    factory1.SetValue(100);

    std::cout<< factory2.GetValue() << endl;

    system("pause");

    return 0;

}

   运行一下结果便可发现,factory2的value被设置成了100而不是默认值0,它的效果就像一个全局变量一样,也就是说这个单件模式可以用来代替全局变量。这个模式的一个好处是,如果你从未使用过Factory::Instance(),都不会进行真正的实例化,即不会分配内存空间,因为是局部静态成员变量,所以只有在真正执行到这句代码了才进行实例化,而且只实例化一次,如果你想自己创建这样的一个实例或者赋值、复制,这都是不允许的,因为构造函数,拷贝构造函数,赋值运算都被声明为私有的,有些没有进行定义,是因为它们不可能会被调用(更多的信息可以查阅Effective C++一书)。但是有点让人不爽的就是必须得声明为引用,也就是说,如果Factory是你自定义类里面的一个数据成员,必须得在成员初始化列表里面进行赋值,像下面那样子:

class MyClass

{

private:

    Factory&f;

    int value;

public:

    MyClass()

    {

f= Factory::Instance();

       value= 0;

    }

};

   这样代码是有错误的,因为引用的定义是,在初始化的时候必须得绑定到一个实例中,而且一旦绑定了一个实例,则不可能绑定到另一个实例,所以上面的使用是不合法的,因此只能在初始化成员列表中进行绑定,像下面那样子:

    MyClass():f(Factory::Instance())

    {

       value= 0;

    }

可以发现,这样子会有很多不便,而且这个工厂类是在栈里面开辟空间的,如果这个工厂类很多数据成员,那将需要很多全局变量空间,那我们能不能在堆里面进行开辟呢?我们能不能让MyClass的第一种写法通过编译呢?能不能使用new进行实例化这个类,但无论new多少次,只有第一次new有效,而不需要Factory::Instance()这么麻烦?答案肯定是可以的,但最后一个问题不在这里进行讨论,因为那个只需要重载operator new (std::size_t)函数即可,但是那样子实现起来会有更多麻烦的问题跳出来,所以不做讨论。我们来看一下《设计模式》中的实现办法,就是四人帮写的那本,下面是实现代码:

class Factory

{

private:

    int value;

    static Factory* factory;

    Factory(){cout<< "Factory" <<endl;value = 0;};

    void operator=(const Factory&);

    Factory(const Factory&);

    ~Factory(){cout<< "~Factory" <<endl;};

public:

    static Factory* Instance()

    {

       if (factory == 0)

       {

           factory= new Factory;

       }

       return factory;

    }

    //…

};

class MyClass

{

private:

    Factory*f;

    int value;

public:

    MyClass()

    {

       f = Factory::Instance();

       value= 0;

    }

};

//cpp文件中

Factory* Factory::factory = 0;

int main()

{

    Factory*factory1 = Factory::Instance();

    Factory*factory2 = Factory::Instance();

    factory1->SetValue(100);

    std::cout<< factory2->GetValue() << endl;

    system("pause");

    return 0;

}

   实际上没改变多少,只是换成了指针来实现,因为初始化时指针指向0,即NULL,即,这个类还没进行实例化,所以就new出来,成功后便指向分配好的那块内存的首地址,当再次调用这个函数的时候,因为指针已经有指向,所以不会再new出来。但是这段代码会出问题,而且是很严重的问题,内存泄漏,看出来是哪里了吗?(上面那段代码是从《设计模式》一书中拷贝出来的,没有进行其他修改,只是将类的名称改了一下)如何知道内存泄漏?运行后会发现没有打印出这"~Factory"这句话,也就是Factory类的析构函数没有调用,而Thinking in C++ 那个版本是会调用的,这可真糟糕,是我的书太老了,版本没更新?还是因为大神们忘了……还是说回收掉了,没有让我们看到?不管怎样,反正是没有调用析构函数,那问题是怎么解决?我们知道,一个static指针,当程序运行完毕后,只是回收到这个指针的内存,一个指针,也就是4个字节的内存(32位机器上),而这个指针所指的内容是不会删除的,像下面那样子使用是合法的:

static int* pInt =new int(10);

void F()

{

    int* pi = pInt;

}

int main()

{

    F();

    std::cout<< *pInt << endl;

    system("pause");

    return 0;

}

   我们知道,一旦F()函数执行完毕后,pi指针会回收掉,但它所指的内容是没有回收的,如何知道?如果pi所指的内容也回收了,那么pInt应该是指向一块没有经过内存分配的空间,而使用pInt指向的值肯定会跳出异常,就像我们delete指针所指的内容,还使用这个指针去进行一些工作,是不合法的。

   那怎么办?还是有办法滴,像下面那样子便可:

class Factory

{

private:

    // 内部类,保存Factory创建出来的指针

    class FactoryPtr;

    friend classFactory::FactoryPtr;

    class FactoryPtr

    {

public:

       Factory*ptr;

       FactoryPtr():ptr(0){};

       ~FactoryPtr()

       {

           if (ptr)

           {

              delete ptr;

              ptr= 0;

           }

       }

    };

private:

    int value;

    static FactoryPtr factory;

    Factory(){cout<< "Factory" <<endl;value = 0;};

    void operator=(const Factory&);

    Factory(const Factory&);

    ~Factory(){cout<< "~Factory" <<endl;};

public:

    static Factory* Instance()

    {

       if (factory.ptr == 0)

       {

           factory.ptr= new Factory;

       }

       return factory.ptr;

    }

    //…

};

//cpp文件中

Factory::FactoryPtr Factory::factory;

这样子的话,在全局区域空间中只需开辟一个指针的大小即可以使用,而不用管Factory到底有多大,只需4个字节即可,这个思想主要是仿智能指针的做法,即用一个对象FactoryPtr来保存创建后的指针,当这个对象的生命后,会自动调用delete,如果将这个对象FactoryPtr拓展,可以兼容任意类型,而不只能是保存Factory,就成了智能指针这个家伙,当然,上面也可以使用智能指针来代替,那样子就可以不用再自定义一个FactoryPtr类,换成staticstd::auto<Factory> factory即可。因为FactoryPtr不应该让其他类看到,也不会给其他类来使用,所以将其定义为Factory的内部类,嗯……内部类,是一个强大的东西,可以解决多重继承,它在这里运用的思想是,四人帮的那句格言:

“对象组合优于类继承”!

   OK?到这里结束了吗?远远还没呢,你的工厂是一个单件,这很好解决,但是如果你在管理很多类,比如键盘、鼠标这些类,你发现,这些类也应该是单件,那怎么办?运用上面的技术的话,需要再自定义KeyboardPtr类和MousePtr类,噢噢噢!!!你会发现出现了重复的工作,这时候你会很不爽,为什么?那你为什么用面向对象的技术?因为复用而且能很好的控制耦合性,可是现在复用没达到。那如果利用这个技术而又能很好的进行复用呢?

这时候你肯定会想能不能有这么一个类,只要继承它,便是一个单体,同时值得注意的是,如果是公有继承,便存在is-a的关系,例如Factory公有继承Singleton,便是在说工厂类是一个单体,OK,有这样一个想法是好的,那怎么实现呢?请看下面:

class Singleton

{

protected:

    template<class Type>

    class SingletonPtr

    {

    private:

       typedef TypePtrType;

    public:

       PtrType* ptr;

       SingletonPtr():ptr(0){};

       ~SingletonPtr()

       {

           if(ptr)

           {

              deleteptr;

              ptr = 0;

           }

       }

    };

    Singleton(){};

    virtual ~Singleton(){};

private:

    Singleton(constJSingleton&);

    Singleton& operator=(const JSingleton&);

};

Factory单体的实现:

class Factory:public Singleton

{

private:

    /…/

    static SingletonPtr<Factory>factory;

    friend class SingletonPtr<Factory>;

/…/

};

//Factory.cpp

Factory::SingletonPtr<Factory>Factory::factory;

       如果你需要把某个类做成单体,那么只需继承Singleton即可,其实在Thinking in C++里也有一个类似的做法,只是这里和它不同的是,它本身是一个模板类,如果用Thinking in C++的技术实现单体的话,应该是这么做:

class Factory:public Singleton<Factory>

{/…/};

而我的做法是将这个模板类做成了内部类,即SingletonPtr<classT>

继续阅读