用于理解C++类中静态成员的单子模式研究

       对于类中的静态成员的使用一直不懂,今天看了一位清华老师的C++课程中的一节课,里面专门利用单子模式对静态成员进行阐释(我不知道老师的姓名,讲的非常好,谢谢!)。下面的记录和理解都是根据我个人的理解和语言总结的,方便以后看时想起,会有不规范之处。

       单子模式:在整个程序生命周期内,一个类只有一个对象,且类中的所有操作都是针对这一个对象的。

       单子模式要求使用人员无法刻意构造新对象,最好也无法析构对象。因此将构造和析构函数都放在private中。下面给出几种单子模式的例子,为方便,类的声明和定义均放在头文件中。

单子模式1:

/*Singleto1.h*/

class Singleton1
{
public:
    static Singleton1* GetSingle()         //静态成员函数,用于产生一个非空的对象指针
    {
        if(!single)
        {
            single = new Singleton1;
        }
        return single;
    }
    int GetA()                            //操作验证数据,每次调用后a都像静态变量一样增加
    {
        return ++a;
    }
private:
    Singleton1(){a = 0;}
    Singleton1(const Singleton1 &that);
    Singleton1 & operator=(const Singleton1 &that);
    ~Singleton1(){;}
private:
    static Singleton1* single;                   //定义指向该类的静态指针成员
    int a;                                       //验证数据
};

/*static*/ Singleton1* Singleton1::single = nullptr;        //静态成员的赋值在类定义外面

      这个类定义中,构造函数、复制构造函数、赋值操作重载函数、析构函数都放在了private中,避免了用户对单子单独行的破坏。检验运行程序如下:

#include<iostream>
#include"Singleton1.h"

using namespace std;

int main()
{
    cout << "First Singleton's address is " << Singleton1::GetSingle() << endl;
    cout << "First call for a is " << Singleton1::GetSingle()->GetA() << endl;
    cout << "Second Singleton's address is " << Singleton1::GetSingle() << endl;
    cout << "Second call for a is " << Singleton1::GetSingle()->GetA() << endl;
}

运行结果:

First Singleton's address is 0x6e0c20
First call for a is 1
Second Singleton's address is 0x6e0c20
Second call for a is 2

      两次运行,单子的地址都没变,且检验数据a的结果也证明两次调用的a是在同一对象内。

      这个类存在一定缺点,函数中new了指针,后面没有delete掉。如果在析构函数中delete掉,如下程序:

/*
错误的析构
*/
~Singleton1()
{
    if (Singleton1::single)
    {
        delete Singleton1::single;
        Singleton1::single = nullptr;
    }
}

      该析构函数无论放在private还是public下都不行,因为delete操作符执行时,本身需要调用Singleton1::single指向的类的析构函数,引起无限套用。且非静态函数不能释放静态指针成员,否则可能导致系统崩溃。

知识点1:静态成员在类的定义中只能声明,其定义在类定义的外面。

知识点2:定义static成员函数,不能用const修饰。

知识点3:静态成员的操作通过静态成员函数进行。

知识点4:通过静态成员函数访问非静态成员,必须指定对象或者使用对象的指针。因为静态成员没有默认的this指针,无法知道具体要访问的是哪个对象的非静态成员。(例子并无体现此知识点)

知识点5:非静态函数不能释放静态指针成员,否则可能会引起系统崩溃。

单子模式2:

/*Singleto2.h*/

class Singleton2
{
public:
    static Singleton2* GetSingle()
    {
        if(!single)
        {
            single = new Singleton2;
        }
        return single;
    }
    int GetA()
    {
        return ++a;
    }
private:
    Singleton2(){a = 0;}
    Singleton2(const Singleton2 &that);
    Singleton2 & operator=(const Singleton2 &that);
    ~Singleton2(){;}
private:
    static Singleton2* single;
    int a;
    //定义了一个专门析构单子的类
    class Destroyer
    {
    public:
        ~Destroyer()
        {
            if (Singleton2::single)
            {
                delete Singleton2::single;
                Singleton2::single = nullptr;
            }
        }
    };
    static Destroyer destroyer;
};

/*static*/ Singleton2* Singleton2::single = nullptr;

      在单子类内部嵌套定义了一个Destroyer类,和Destroyer类的静态对象(对于Singleton2类,该静态对象是静态成员)。Destoryer类只用于析构单子。当程序结束时,系统自动调用Destroyer类静态成员的析构函数,同时析构了单子new出来的single指针。

知识点6:程序结束时,系统自动调用静态成员的析构函数。

      该销毁只能在程序结束时进行,销毁时机不能自己掌握。且在程序结束时,系统将自动回收资源,所以销毁的动作并无意义。有些系统也可能执行不到销毁的步骤,直接释放资源。

单子模式3:

/*Singleto3.h*/
#include<cstdlib>

class Singleton3
{
public:
    static Singleton3* GetSingle()
    {
        if(!single)
        {
            single = new Singleton1;
        }
        return single;
    }
    int GetA()
    {
        return ++a;
    }
    //不调用析构函数,定义Release函数释放new出来的指针,时机自行掌握
    static void Release()           
    {
        if (single)
        {
            free(single);
            single = nullptr;
        }
    }
private:
    Singleton1(){a = 0;}
    Singleton1(const Singleton1 &that);
    Singleton1 & operator=(const Singleton1 &that);
    ~Singleton1(){;}
private:
    static Singleton1* single;
    int a;
};

/*static*/ Singleton1* Singleton1::single = nullptr;

       这里有一个奇怪的地方,即new出来的指针用free()释放内存,有悖于new/delete,malloc()/free()的配对使用原则。不同在于,使用delete操作符,需要调用Singleton3的析构函数;使用free()只是简单的返还内存给操作系统,跳过了析构函数。实测有效。但还是建议将

single = new Singleton1;

改为:

single = (Singleton3*)malloc(sizeof(Singleton3));

测试程序:

#include<iostream>
#include"Singleton3.h"

using namespace std;

int main()
{
    cout << "First Singleton's address is " << Singleton3::GetSingle() << endl;
    cout << "First call for a is " << Singleton3::GetSingle()->GetA() << endl;
    cout << "Second Singleton's address is " << Singleton3::GetSingle() << endl;
    cout << "Second call for a is " << Singleton3::GetSingle()->GetA() << endl;
    //调用Release()函数释放内存
    Singleton3::GetSingle()->Release();   
    cout << "Release() called." << endl;
    //重新给出对象指针
    cout << "Singleton's address is " << Singleton3::GetSingle() << endl;
    cout << "Restart Call for a is " << Singleton3::GetSingle()->GetA() << endl;
}

测试结果:

First Singleton's address is 0x1453c20
First call for a is 1
Second Singleton's address is 0x1453c20
Second call for a is 2
Release() called.
Singleton's address is 0x1453c20
Restart Call for a is 1

      可以看出,两次调用释放内存前后,单子的地址并没有变化,但是a的直在释放内存后重新开始计数。地址无变化本人理解为程序在堆区并没有利用其他内存,所以第一次释放内存后,第二次还是在原来的内存处重新建立新对象。虽然地址相同,但是两个不同的单子。

单子模式4:

/*Singleton4.h*/

class Singleton4
{   
public:
    static Singleton4 &GetSingle()
    {
        static Singleton4 single;
        return single;
    }
    int GetA()
    {
        return ++a;
    }
private:
    Singleton4(){a = 0;}
    Singleton4(const Singleton4 &that);
    Singleton4 & operator=(const Singleton4 & that);
    ~Singleton4(){;}
private:
    int a;
};

      这里把在static成员函数GetSingle()内部定义单子的static对象,每次调用GetSingle()得到的都是static局部量single。因为single是一个引用,所以使用的时候可以定义另一个引用,并用GetSingle()对其赋值。虽然实际上是单子的引用,但是看起来与单子的意义有悖。

Singleton4 &single = Singleton4::GetSingle();
int n = single.GetA();

      单子模式4是不能在任意位置析构的(single是static类,在静态区)。但好处在于并未在堆区new出指针,避免了归还内存的麻烦。所有静态变量在程序结束后由系统自动调用析构函数进行销毁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值