1. 程式人生 > >單例模式——餓漢模式

單例模式——餓漢模式

所謂的單例模式,就是設計一個類,它在整個程式中只能有一個該類的例項存在,這就是單例模式。

C++實現單例模式的一般方法是將建構函式,拷貝建構函式以及賦值運算子函式宣告成private,從而禁止他人建立例項。否則如果上面三者不為私有,那麼他人就可以呼叫上面的三個函式來建立例項,就沒法實現單例模式。但是我們總歸是要建立一個類的,我們可以提供一個public的靜態方法來幫助我們獲得這個類唯一的一個例項化物件

單例模式一般有兩種實現模式

  • 餓漢模式:像一個餓漢一樣,不管需不需要用到例項都要去建立例項,即在類產生的時候就建立好例項,這是一種空間換時間的做法。作為一個餓漢而言,體現了它的本質——“我全都要”。 

  • 懶漢模式:像一個懶漢一樣,需要用到建立例項了程式再去建立例項,不需要建立例項程式就“懶得”去建立例項,這是一種時間換空間的做法,這體現了“懶漢的本性”。

在這篇文章中,我們暫且只來討論餓漢模式

       餓漢模式的物件在類產生時候就建立了,一直到程式結束才會去釋放。即作為一個單例類例項,它的生存週期和我們的程式一樣長。因此該例項物件需要儲存在全域性資料區,所以肯定需要使用static來修飾,因為類內部的static成員是不屬於每個物件的,而是屬於整個類的。在載入類的時候,我們的例項物件就產生了。所以對於餓漢模式而言,是執行緒安全的,因為線上程建立之前例項已經被建立好了。

下面我們可以來模式實現一個餓漢模式的單例類

singleton.hpp

#pragma once

#include <iostream>
using namespace std;

class Singleton{
private:
    Singleton(){
        cout << "建立了一個單例物件" << endl;
    }
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);
    ~Singleton(){
        //解構函式我們也需要宣告成private的
        //因為我們想要這個例項在程式執行的整個過程中都存在
        //所以我們不允許例項自己主動呼叫解構函式釋放物件
        cout << "銷燬了一個單例物件" << endl;
    }


    static Singleton instance;  //這是我們的單例物件,注意這是一個類物件,下面會更改這個型別
public:
    static Singleton* getInstance();
};

//下面這個靜態成員變數在類載入的時候就已經初始化好了
Singleton Singleton::instance; 

Singleton* Singleton::getInstance(){
    return &instance;
}

test.cc

#include "singleton.hpp"

int main(){
    cout << "Now we get the instance" << endl;
    Singleton* instance1 = Singleton::getInstance();
    Singleton* instance2 = Singleton::getInstance();
    Singleton* instance3 = Singleton::getInstance();
    cout << "Now we destroy the instance" << endl;
    return 0;
}

我們來看下執行結果

由此可見,對於我們的程式,在我們輸出Now we get the instance這句話的時候,也就是我們即將獲取到這個類物件的例項的時候,在這之前這個單例類的例項在載入類的時候已經被建立好了,且我們呼叫了三個getInstance來獲取例項,也並沒有因此而多建立更多的例項,因此它是一個單例類。在程式結束的時候,這個唯一的例項才會被銷燬。

上面我們在singleton.hpp 中實現的例項是一個類物件,現在我們看看來把它換成類物件的指標會怎麼樣

singleton.hpp

#pragma once

#include <iostream>
using namespace std;

class Singleton{
private:
    Singleton(){
        cout << "建立了一個單例物件" << endl;
    }
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);
    ~Singleton(){
        //解構函式我們也需要宣告成private的
        //因為我們想要這個例項在程式執行的整個過程中都存在
        //所以我們不允許例項自己主動呼叫解構函式釋放物件
        cout << "銷燬了一個單例物件" << endl;
    }
    static Singleton* instance;  //這是我們的單例物件,它是一個類物件的指標
public:
    static Singleton* getInstance();
};

//下面這個靜態成員變數在類載入的時候就已經初始化好了
Singleton* Singleton::instance = new Singleton(); 

Singleton* Singleton::getInstance(){
    return instance;    //這裡就是直接返回instance了而不是返回&instance
}

測試程式不變,我們再來看一下程式執行結果

 這是怎麼回事???程式居然沒有呼叫解構函式??那麼會有什麼後果?沒有釋放佔有的資源,導致記憶體洩漏!!

這是為什麼呢?因此此時全域性資料區中,儲存的並不是一個例項物件,而是一個例項物件的指標,它是一個地址而已,我們真正佔有資源的例項物件是儲存在堆中的。這樣的宣告方法可以減小全域性資料區的佔用量,把一大堆單例物件放在了堆中,但我們需要主動地去呼叫delete釋放申請的資源。我們想要手動呼叫delete 直接釋放該例項是不可能的,因為它的解構函式是私有的,調不到解構函式(解構函式是私有也是我們要求的)。

delete Singleton::getInstance;    //這編譯不過,調不到解構函式

方法一:在類中再寫一個主動釋放資源的方法

singleton.hpp

#pragma once

#include <iostream>
using namespace std;

class Singleton{
private:
    Singleton(){
        cout << "建立了一個單例物件" << endl;
    }
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);
    ~Singleton(){
        //解構函式我們也需要宣告成private的
        //因為我們想要這個例項在程式執行的整個過程中都存在
        //所以我們不允許例項自己主動呼叫解構函式釋放物件
        cout << "銷燬了一個單例物件" << endl;
    }
    static Singleton* instance;  //這是我們的單例物件,它是一個類物件的指標
public:
    static Singleton* getInstance();
    static void deleteInstance();   //用來銷燬例項
};

//下面這個靜態成員變數在類載入的時候就已經初始化好了
Singleton* Singleton::instance = new Singleton(); 

Singleton* Singleton::getInstance(){
    return instance;   
}
void Singleton::deleteInstance(){
    delete instance;
}

test.cc

#include "singleton.hpp"

int main(){
    cout << "Now we get the instance" << endl;
    Singleton* instance1 = Singleton::getInstance();
    Singleton* instance2 = Singleton::getInstance();
    Singleton* instance3 = Singleton::getInstance();
    cout << "Now we destroy the instance" << endl;
    Singleton::deleteInstance();
    return 0;
}

程式執行結果如下圖

但是這就讓很多程式設計師不爽了,因為他們(哈哈當然還有我)總是會忘記手動去呼叫函式來釋放資源。於是我們想到了,可不可以有一個自動地能夠釋放資源的函式。

方法二: 定義一個內部的類

singleton.hpp

#pragma once

#include <iostream>
using namespace std;

class Singleton{
private:
    Singleton(){
        cout << "建立了一個單例物件" << endl;
    }
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);
    ~Singleton(){
        //解構函式我們也需要宣告成private的
        //因為我們想要這個例項在程式執行的整個過程中都存在
        //所以我們不允許例項自己主動呼叫解構函式釋放物件
        cout << "銷燬了一個單例物件" << endl;
    }

    static Singleton* instance;  //這是我們的單例物件,它是一個類物件的指標
public:
    static Singleton* getInstance();

    
private:
    //定義一個內部類
    class Garbo{
    public:
        Garbo(){}
        ~Garbo(){
            if(instance != NULL){
                delete instance;
                instance = NULL;
            }
        }
    };

    //定義一個內部類的靜態物件
    //當該物件銷燬的時候,呼叫解構函式順便銷燬我們的單例物件
    static Garbo _garbo;
};

//下面這個靜態成員變數在類載入的時候就已經初始化好了
Singleton* Singleton::instance = new Singleton(); 
Singleton::Garbo Singleton::_garbo;     //還需要初始化一個垃圾清理的靜態成員變數

Singleton* Singleton::getInstance(){
    return instance;   
}

test.cc

#include "singleton.hpp"

int main(){
    cout << "Now we get the instance" << endl;
    Singleton* instance1 = Singleton::getInstance();
    Singleton* instance2 = Singleton::getInstance();
    Singleton* instance3 = Singleton::getInstance();
    cout << "Now we destroy the instance" << endl;
    return 0;
}

程式執行結果如下

我們成功地銷燬了物件,而且還沒有手動去釋放!Perfect!當然了,我們想到,為什麼不嘗試用一用智慧指標呢?智慧指標不就是為了能夠讓我們不需要手動釋放資源而設計的麼,它會自動去釋放資源啊?關於智慧指標的問題,不懂的朋友去看一看我的另一篇部落格: C++中的智慧指標。現在我們嘗試著用智慧指標試試建立一個單例類。

singleton.hpp

#pragma once

#include <iostream>
#include <memory>
using namespace std;

class Singleton{
private:
    Singleton(){
        cout << "建立了一個單例物件" << endl;
    }
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);
    ~Singleton(){
        //解構函式我們也需要宣告成private的
        //因為我們想要這個例項在程式執行的整個過程中都存在
        //所以我們不允許例項自己主動呼叫解構函式釋放物件
        cout << "銷燬了一個單例物件" << endl;
    }

    static shared_ptr<Singleton> instance;  //這是我們的單例物件,它是一個類物件的指標
public:
    static shared_ptr<Singleton> getInstance();
};
    

//下面這個靜態成員變數在類載入的時候就已經初始化好了
shared_ptr<Singleton> Singleton::instance(new Singleton()); 

shared_ptr<Singleton> Singleton::getInstance(){
    return instance;    
}

test.cc

#include "singleton.hpp"

int main(){
    cout << "Now we get the instance" << endl;
    shared_ptr<Singleton> instance1 = Singleton::getInstance();
    shared_ptr<Singleton> instance2 = Singleton::getInstance();
    shared_ptr<Singleton> instance3 = Singleton::getInstance();
    cout << "Now we destroy the instance" << endl;
    return 0;
}

你會發現它甚至連編譯都過不了,原因是shared_ptr無法訪問私有化的解構函式。但是我們又需要解構函式是私有的,這就矛盾起來了(為什麼希望解構函式是私有的如上註釋)。因此,我們就需要用到shared_ptr可以指定刪除器的特點,自定義刪除器。

方法三:shared_ptr與自定義刪除器

singleton.hpp

#pragma once

#include <iostream>
#include <memory>
using namespace std;

class Singleton{
private:
    Singleton(){
        cout << "建立了一個單例物件" << endl;
    }
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);
    ~Singleton(){
        //解構函式我們也需要宣告成private的
        //因為我們想要這個例項在程式執行的整個過程中都存在
        //所以我們不允許例項自己主動呼叫解構函式釋放物件
        cout << "銷燬了一個單例物件" << endl;
    }
    static void DestroyInstance(Singleton*);    //自定義一個釋放例項的函式

    static shared_ptr<Singleton> instance;  //這是我們的單例物件,它是一個類物件的指標
public:
    static shared_ptr<Singleton> getInstance();
};
    

//下面這個靜態成員變數在類載入的時候就已經初始化好了
shared_ptr<Singleton> Singleton::instance(new Singleton(),Singleton::DestroyInstance); 

shared_ptr<Singleton> Singleton::getInstance(){
    return instance;    
}
void Singleton::DestroyInstance(Singleton*){
    cout << "在自定義函式中釋放例項" << endl;
}

test.cc

#include "singleton.hpp"

int main(){
    cout << "Now we get the instance" << endl;
    shared_ptr<Singleton> instance1 = Singleton::getInstance();
    shared_ptr<Singleton> instance2 = Singleton::getInstance();
    shared_ptr<Singleton> instance3 = Singleton::getInstance();
    cout << "Now we destroy the instance" << endl;
    return 0;
}

程式的執行結果如下

好了,餓漢模式的單例類講完了,因為單例模式在程式一開始就初始化好例項,所以後續不再需要考慮執行緒安全的問題,因此它適用於執行緒比較多的程式中,以空間換取時間,提高了效率。

但在懶漢模式中,情況就不一樣了,因為它是在使用時才建立例項,在第一次呼叫getInstance()的時候,才建立例項物件。如果有多個執行緒,同時呼叫了getInstance()獲取例項,那麼可能就會產生多個例項,那麼這就不是我們的單例模式了。因此我們需要做一點事情,才能夠避免這種情況的發生。可以看看我的下一篇文章單例模式的懶漢模式

相關推薦

與懶漢模式

單例模式 一個類只建立一個例項,每次都獲取的同一個物件 單例模式分為餓漢模式和懶漢模式 單例模式的三要素: 1、構造方法私有化 2、靜態屬性指向例項 3、public static 的xx方法要返回例項 餓漢模式 立即載入,無論是否用到這個物件都會載入 無論如何

模式——模式

所謂的單例模式,就是設計一個類,它在整個程式中只能有一個該類的例項存在,這就是單例模式。 C++實現單例模式的一般方法是將建構函式,拷貝建構函式以及賦值運算子函式宣告成private,從而禁止他人建立例項。否則如果上面三者不為私有,那麼他人就可以呼叫上面的三個函式來建立例項

類設計模式 --- 模式 & 懶漢模式

/* 單例設計模式:保證一個類在記憶體中只有一個物件。 模式:模式就是解決 一類 問題的固定步驟 。 模式的概念最早起源於建築行業.... 建房子的步驟都是一樣子: 打地基-----> 澆柱子------->蓋樓面---------&

式 懶漢式

<strong></strong><pre name="code" class="java"><strong>【餓漢式】</strong> public class Single { private stat

多線程模式之立即加載(模式)

run tel ext 相同 turn nbsp 加載 一個 nis package com.wz.thread.immediately;/** * 立即加載/餓漢模式 單例設計模式 * @author Administrator * */public class MyOb

對象初始化過程與設計模式(式與懶漢式)

得到 延時 兩個 都是 person 其他 導致 最大 類屬性 1.對象初始化過程:(先加載類到內存,然後加載類屬性,成員方法)    定義一個類Person, 在new Person("zhangsan",20); 初始化過程: 因為new 用到Person.clas

第六章模式與多執行緒——立即載入“模式”與延遲載入“懶漢模式

立即載入就是使用類的時候已經將物件建立完畢了,也稱為“餓漢模式” package test01; public class MyObject { // 建立物件 private static MyObject object = new MyObject(); private MyObjec

模式下的懶漢和模式

1 //單例模式---懶漢模式 2 public class Apple{ 3 //建立一個成員,在記憶體中只有一個拷貝 4 private static Apple apple = null; 5 private Apple(){ 6 7

設計模式--式與懶漢式小結

單例設計模式(Singleton)也叫單態設計模式 什麼是單例設計模式? 單例設計模式  要求一個類有且僅有一個例項,並且提供了一個全域性的訪問點。這個單例不應該由人來控制,而應該由程式碼來限制。 單例設計模式的思路:   1.私有化建構函式   2.私有化靜態類變數   3.對外提供獲

模式、懶漢模式以及模式的區別及應用

1.單例模式 單例模式就是系統執行期間,有且僅有一個例項。它有三個必須滿足的關鍵點: (1)一個類只有一個例項。這是滿足單例模式最基本的要求,若滿足這個關鍵點,只能提供私有的構造器,即保證不能隨意建立該類的例項。示例如下: //讀取配置檔案的工具類—單例模式 public class Con

【C++】模式模式和懶漢模式

餓漢模式:提前建立一個靜態的類物件,把所有能夠建立物件的模組全部私有化,從外部需要建立類物件時只能返回事先建立好的唯一物件。就像一個特別飢餓的人,提前準備好食物,只要餓了,就可以立刻食用。 /*惡漢模式--單例模式*/ #include<iostream> using namespa

20、設計模式模式-

import java.io.Serializable; /** * 餓漢式-單例模式 * 實現Serializable介面,使其支援序列化與反序列化 */ public class HungrySingleton implements Serializable {

模式中懶漢模式模式

/** * @author 萬星明 * @version 建立時間:2018年10月26日 下午4:32:10 * 請編寫一個單例模式,類名自己定義(不允許出現無意義命名)。 * 分別用懶

模式--、懶漢、多線程以及多線程下改進

desc 懶漢 http ret locker syn bubuko 線程 info 代碼註釋有介紹 package singleton; /** * @author: ycz * @date: 2018/12/24 0024 22:15 * @descripti

模式--(按需建立)、懶漢(多執行緒以及多執行緒下改進)

程式碼註釋有介紹,(排版過於難受,下次用md排完再傳上來) package singleton; /** * @author: ycz * @date: 2018/12/24 0024 22:15 * @description: 懶漢模式 */ public class SingleTon1

模式---懶漢與模式和靜態內部類實現

單例模式是最基本的 java 設計模式之一 主要有兩種設計方法,主要分為餓漢式與懶漢式 餓漢式比較簡單,直接使用常量即可,主要程式碼如下: private static final SingleModel INSTANCE = new Sing

設計模式(Java)-003-模式-

餓漢式(執行緒安全、呼叫效率高、但是不能延遲載入) 1.執行緒安全:在類載入時完成物件的建立 2.呼叫效率高:getInstance沒有任何同步限制 3.不能延遲載入:正是因為在類載入時就完成了

設計模式模式-式&懶漢式

定義:保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。 Java中單例模式定義:“一個類有且僅有一個例項,並且自行例項化向整個系統提供。” 通過單例模式可以保證系統中一個類只有一個例項而且該例項易於外界訪問,從而方便對例項個數的控制並節約系統資源。

設計模式-模式-和懶漢式

單例模式(Singleton pattern)一個在設計模式中比較簡單的模式,我們常說的餓漢式和懶漢式是最常舉例的兩種寫法。 如下 餓漢式: public class Singleton { private static final Singleton sin

設計模式模式(式+多執行緒情況下的懶漢式)

  今天所記錄的補上昨天未完成的設計模式之單例模式 餓漢式單例:    執行結果:    在編寫餓漢式單例時使用了final 關鍵字進行修飾所以不會出現多執行緒安全的情況產生。接下來我們完善一下昨天的懶漢式單例:   首先我們看一下昨天懶漢式單例的執行圖:        發現