C++学习之第九天-自动释放单例模式

编程题

1、实现单例模式的自动释放(4种方式)

1.1友元类实现单例模式的释放。

知识点:

        要实现自动释放,可以考虑用一个栈对象,一个类在栈上创建一个对象,在程序执行结束后,栈对象空间被自动释放,会去调用该类的析构函数。把释放单例模式指针的代码放在该类中就可以实现自动释放。

#include <iostream>
using namespace std;

class AutoRelease;//1.要在单例模式中作为友元,所以先进行声明

class Singleton//2.单例模式类。
{
    friend class AutoRelease;//自动释放友元
public:

    static Singleton *get_Instance()//获取单例指针的接口,静态
    {
        if(nullptr==_pInstance) //指针为空的时候才创建,一开始设单例指针为nullptr,在多线程的时候会出问题,这里不考虑多线程的情况,不过也好解决。
        {
            _pInstance = new Singleton();//单例指针不存在的情况下就创建
        }

        return _pInstance;//不空直接返回当前指针
    }
private://构造函数和析构函数放在私有权限下,为了防止创建多个对象
    Singleton()//构造函数
    {
        cout<<"Singleton()"<<endl;

    };

    ~Singleton()//析构
    {
        cout<<"~Singleton()"<<endl;
    }

    static Singleton *_pInstance;//静态单例指针,类内声明,类外定义
};
Singleton *Singleton::_pInstance = nullptr;//饱汉

class AutoRelease
{
public:
    AutoRelease()
    {
        cout<<"AutoRelease()"<<endl;
    }

    ~AutoRelease()
    {

        if(Singleton::_pInstance) //栈对象销毁时自动调用,释放单例指针的函数放在友元类的析构函数里
        {
            delete Singleton::_pInstance;
            Singleton::_pInstance = nullptr;
            cout<<"delete _pInstance"<<endl;
        }
        cout<<"AutoRelease"<<endl;
    }

};
void test01()
{
    Singleton *ps1 = Singleton::get_Instance();
    Singleton *ps2 = Singleton::get_Instance();

    cout<<ps1<<endl;
    cout<<ps2<<endl;
    AutoRelease ar;//自动释放。
}
int main()
{
    test01();
    return 0;
}

1.2 嵌套类释放单例模式指针

注意点:

1.把释放类放在单例类里面,单例模式类里面多一个释放类对象,必须是静态变量,为何?

        释放类的对象不能作为普通成员函数,如果作为普通成员函数,单例指针无法进行释放,释放类对象和单例指针作为同一个类的成员,由于在同样的作用域下,Singleton和AutoRelease的析构函数形成死锁(这种情况下,Release类对象的释放要Singleton的析构函数执行才能销毁,而Singleton的销毁又需要Release的析构函数执行才能完成)。

        因此,释放类的对象不能和单例指针在同一作用域下,而作为静态变量,程序执行结束后,释放类对象被销毁,其析构函数被调用,单例类才能被销毁。

#include <iostream>
using namespace std;

class Singleton
{
public:
    class AutoRelease //嵌套类
    {
    public:
        AutoRelease()
        {
            cout<<"AutoRelease()"<<endl;
        }

        ~AutoRelease()
        {

            if(_pInstance)//释放函数写在自动类的析构函数里,程序执行结束自动释放
            {
                delete _pInstance;
                _pInstance = nullptr;
                cout<<"Delete _pInstance succeed"<<endl;
            }
            cout<<"~AutoRelease()"<<endl;
        }
    };

    static Singleton* get_Instance()//获取单例指针
    {
        if(nullptr==_pInstance)//指针为空才创建
        {
            _pInstance = new Singleton();
        }
        return _pInstance;
    }

private:
    Singleton()
    {
        cout<<"Singleton()"<<endl;
    }

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

    static Singleton* _pInstance;//单例指针
    static AutoRelease _autoRe;//1.不能作为普通成员函数,如果作为普通成员函数,单例指针无法进行释放,
                                //AutoRelease对象和_pInstance指针作为同一成员,在同样的作用域,
                                //Singleton和AutoRelease的析构函数形成死锁.因此AutoRelease的对象只能作为静态变量
};
Singleton *Singleton::_pInstance=nullptr;//单例指针一开始为空
Singleton::AutoRelease Singleton::_autoRe;//实例化释放类对象,

int main()
{
    Singleton *ps1 = Singleton::get_Instance();

    Singleton *ps2 = Singleton::get_Instance();
    cout<<ps1<<endl;
    cout<<ps2<<endl;


    return 0;
}

1.3 用atexit自动释放单例模式

知识点:

1.atexit在stdlib.h头文件下,用于对一个函数进行注册,等main()中其他函数执行完毕后才执行这个经过atexit注册的函数。

  1. 在初始化单例指针的时候,要把指针设置成饿汉模式-即程序一开始就给指针申请空间(在main函数开始之前,单例指针不空),否则,在多线程下,饱汉模式(指针设为nullptr),每个线程的单例指针都是nullptr,都会跑去新创建。

  2. 实现:1.在单例类设一个销毁函数,在创建完指针后,用atexit注册一下即可。

atexit示例:

#include <iostream>
#include <stdlib.h>
using namespace std;

void func()
{
    cout<<"void func()"<<endl;
}
int main()
{

    cout<<"enter main...."<<endl;
    atexit(func);
    cout<<"end main....."<<endl;
    return 0;
}
/*
输出:
enter main....
end main.....
void func()
*/

用atexit自动释放单例指针

#include <iostream>
using namespace std;
#include<stdlib.h>

class Singleton
{
public:
    static Singleton* get_Instance()
    {
        if(!_pInstance)
        {
            _pInstance = new Singleton();
            atexit(Destory); //单例指针初始化后,立即给指针注册释放函数,等程序运行结束进行释放
        }
        return _pInstance;
    }
private:
    Singleton()
    {
        cout<<"Singleton()"<<endl;
    }

    static void Destory()
    {
        if(_pInstance)
        {
            delete _pInstance;
            _pInstance = nullptr;
            cout<<"自动释放单例指针.....完成"<<endl;
        }
        
    }

    ~Singleton()
    {
        cout<<"~Singleton()"<<endl;
    }
    static Singleton *_pInstance;
};
Singleton *Singleton::_pInstance = Singleton::get_Instance();
//如果这里是nullptr,在多线程环境下,这代码是极其不安全的多线程环境下,设为nullptr,每个线程都会在get_Instance申请空间解决方法,在main函数之前,保证_pInstance不为空
int main()
{
    Singleton *ps1 = Singleton::get_Instance();
    Singleton *ps2 = Singleton::get_Instance();
    cout<<ps1<<endl;
    cout<<ps2<<endl;
    return 0;
}

1.4. pthread+atexit自动释放单例指针。

知识点:

#include <pthread.h>里面的pthread_once(&once,init)函数

        1.static pthread_once_t once;//控制pthread_once 函数中的初始变量

        2.pthread_once_t Singleton::once = PTHREAD_ONCE_INIT;//设置宏标记,init执行一次后,会变

        3.pthread_once功能:保证放在init函数里面的函数体,在多线程的条件下,里面的代码只执行一次。

#include <iostream>
#include <pthread.h>
#include <stdlib.h>
using namespace std;
class Singleton
{
public:
    static Singleton* get_Instance()
    {
        pthread_once(&once,init);
        //init里面的函数,在多线程环境下,只去执行一次
        return _pInstance;
    }

    static void init()
    {
        _pInstance = new Singleton();//创建
        atexit(Destory);//注册销毁函数
    }
private:
    Singleton()
    {
        cout<<"Singleton()"<<endl;
    }

    static void Destory()
    {
        if(_pInstance)
        {
            delete _pInstance;
            _pInstance = nullptr;
            cout<<"自动释放单例指针.....完成"<<endl;
        }
        
    }

    ~Singleton()
    {
        cout<<"~Singleton()"<<endl;
    }
    static Singleton *_pInstance;
    static pthread_once_t once;//控制pthread_once 函数中的初始变量
};
Singleton *Singleton::_pInstance = Singleton::get_Instance();//如果这里是nullptr,在多线程环境下,这代码是极其不安全的
pthread_once_t Singleton::once = PTHREAD_ONCE_INIT;

int main()
{
    Singleton *ps1 = Singleton::get_Instance();
    Singleton *ps2 = Singleton::get_Instance();

    cout<<ps1<<endl;
    cout<<ps2<<endl;

}

2、实现COW的String,让其operator[]能够区分出读写操作

COW原理:

        string发生拷贝构造或者赋值的时候,不会复制字符串的内容,而是增加一个引用计数,字符串指针进行浅拷贝,当需要对字符串内容进行修改或者写操作时,才进行深拷贝。

2.实现。

1.给管理字符串指针多申请4个字节的空间大小,前4个字节用来存储引用计数,用解引用方式来访问引用计数值--重要

2.字符串发生拷贝构造操作的时候和赋值操作的时候,只进行浅拷贝,执行引用计数+1

3.发生取下标操作的时候,先判断引用计数的值,再进行操作,目前operator[]不能够区分出读写操作,对下标操作的读写都会进行深拷贝

4.析构函数:在引用计数为0的情况才释放。

3.String运算符重载总结

1.左移运算符<<,在String类声明为友元:
  
 1.返回类型是 ostream&,两个形参:第一个ostream &os,第二个String对象的引用

2.赋值运算符重载:
    1
.返回类型: String对象的引用;一个参数:String对象的引用或者const char *
    2.步骤:自复制、释放左操作数、深拷贝、返回*this
    
3.+=运算符重载:
    
1.返回类型:String对象的引用;一个参数:String对象的引用或者const char *
    2.步骤:记录新字符串大小、请一个临时堆空间,存储接起来的内容、释放左操作数、建立联系、返回*this
    
4.[]下标运算符重载:
 
   1.返回类型:char类型的引用:char&,一个参数:索引位置 int pos;
    2.步骤:下标小于字符串大小才允许返回,否则返回一个静态char变量'\0'

5.关系运算符重载:==,>=,<=,>,<,友元形式存在
  
 1.返回类型bool, 两个参数:都是类对象的引用。
    
6.右移运算符重载:>>输入,友元形式存在

    1.返回类型:istream的引用;两个参数:istream对象的引用,string对象的引用
    2.步骤:
        1.先清空原堆区内容 
        2.弄个临时堆区或者用vector,来存取从键盘输入的数据 
        3.申请新空间,进行深拷贝    
        4.返回istream对象


7.+加号运算符重载进行字符串拼接:--友元形式存在
 
   1.返回类型:String对象,两个参数:都是String类对象的引用或者一个对象的引用+const char*
    2.步骤:
        1.记录新字符串大小
        2.申请一个临时堆空间,存储接起来的内容
        3.实例化新的String对象,2的内容作为初始值,删除临时堆空间
        4.返回String临时对象。

版本1:不区分读写操作

头文件:05_cow.h

#include <iostream>
#include <stdlib.h>
using namespace std;
#include <string.h>

class String
{
public:
    //1.无参构造函数
    String();

    //2.拷贝构造函数
    String(const char *_pstr);

    //3.拷贝构造函数
    String(const String &rhs);

    //4.赋值运算符函数
    String &operator=(const String &rhs);

    //5.析构函数
    ~String();

    //6.左移运算符重载输出
    friend ostream& operator<<(ostream &os, const String &rhs)
    {
        if(rhs._pstr)
        {
            os<<rhs._pstr;
        	return os;
        }
        
    }

    //7.打印对象的堆空间地址
    void print_Address()
    {
        printf("%p\n", _pstr);
    }

    //8.重载下标运算符,进行读写访问都会进行深拷贝
    char &operator[](int pos);

    //9.返回c语言风格字符串
    const char *c_str()
    {
        return _pstr;
    }
private:
    void InitRefcount()
    {
        //初始化引用计数
        *(int *)(_pstr-4)=1;
    }
    int getRefcount()
    {
        //获取引用计数
        return *(int *)(_pstr-4);
    }
    //引用计数加1
    void IncreseRefcount()
    {
        ++*(int *)(_pstr-4);
    }
	//引用计数减1
    void DecreaseRefcount()
    {
        --*(int *)(_pstr - 4);
    }
    //字符串长度
    int size()
    {
        return strlen(_pstr);
    }
    
    char *_pstr;
};

cou.cc文件

#include "05_cow.h"


//1.无参构造函数
String::String()
    :_pstr(new char[5]()+4)
{
    cout<<"String()"<<endl;
    //1.初始化引用计数
    // ++*(int *)(_pstr-4);
    InitRefcount();
}

//2.拷贝构造函数
String::String(const char *str)
    :_pstr(new char[strlen(str)+5]() + 4)
{
    cout<<"String(const char *str)"<<endl;

    InitRefcount();
    strcpy(_pstr, str);
}

//3.拷贝构造函数
String::String(const String &rhs)
    :_pstr(rhs._pstr)
{
    cout<<"String(const String &rhs)"<<endl;
    IncreseRefcount(); //引用计数++
}

//4.赋值运算符函数
String& String::operator=(const String &rhs)
{
    cout<<"String &operator=(const String&rhs)"<<endl;

    if(this!=&rhs)//自复制
    {
        //2.释放左操作数
        delete _pstr;
        _pstr = nullptr;

        //3.进行浅拷贝
        _pstr = rhs._pstr;

        //4.引用计数加1
        IncreseRefcount();

    }
    return *this;

}

//5.析构函数

String::~String()
{
    DecreaseRefcount(); //引用计数减1
    if(0==getRefcount()) //引用计数为0,则删除
    {
        delete [] (_pstr-4);
    }
}

//重载下标运算符函数,读写都会进行深拷贝
char &String::operator[](int pos)
{
    if(pos<size())
    {
        if(this->getRefcount()>1) 贝
        {//引用计数值大于1,指向这内存的不止一个指针,重新申请空间,进行深拷
            DecreaseRefcount();

            char *temp = new char[strlen(_pstr)+5]()+4;

            strcpy(temp,_pstr);

            _pstr = temp;
            InitRefcount();  
        }
	    //不大于1,说明指向堆空间的指针只有一个,可以直接进行操作
        return _pstr[pos];


    }
    else
    {
        static char charnull='\0';
        return charnull;
    }

}

main.cc

#include "05_cow.h"
int main()
{
    String p1("hello world");
    String p2(p1);
    
    cout<<p1<<endl; //打印对象
    cout<<p2<<endl;

    p1.print_Address();//获取堆内存地址
    p2.print_Address();
    
    cout<<p2[0]<<endl;//下标进行读取
    p2.print_Address();//获取地址,地址变了


    cout<<p2.c_str()<<endl;
    return 0;
}

版本2:operator区分读写操作

待完成。

猜你喜欢

转载自blog.csdn.net/weixin_49278191/article/details/121148622