Xây dựng mẫu Singleton Template trong C++

From CodeForLife
Jump to: navigation, search

Singleton là một mẫu thiết kế thông dụng nhất và được sử dụng thường xuyên để đảm bảo chỉ có một đối tượng duy nhất được tạo ra và có thể truy xuất mọi lúc, mọi nơi trong chương trình.

Để cài đặt một lớp Singleton đảm bảo đúng đặc tính của nó, bạn phải chú ý vấn đề sau:

  • Hàm khởi tạo (Constructor) và hàm hủy (Destructor) phải là Private => Việc này đảm bảo từ bên ngoài hoặc ở lớp kế thừa không thể khởi tạo được.
  • Trong lớp phải có hàm public static để khởi tạo đối tượng, thường người ta hay đặt tên hàm đó là GetInstance() => Việc lấy đối tượng sẽ đều phải thông qua hàm này.

Việc tạo lớp Singleton rất đơn giản, nhưng ở bài viết này tôi muốn giới thiệu với các bạn xây dựng Singleton dạng Template để tối ưu code và sử dụng thêm SmartPoiter để tối ưu quản lý bộ nhớ trong Singleton.

Cài đặt Singleton đơn giản

Dưới đây là ví dụ cài đặt Singleton đơn giản cho lớp Manager. Bạn chú ý:

  • Hàm khởi tạo và hàm hủy là private
  • Khai báo biến tĩnh m_Instance và hàm GetInstance() trả về địa chỉ biến tĩnh này.

Tệp Manager.h:

#ifndef MANAGER_H
#define MANAGER_H

class Manager {
public:
    static Manager* GetInstance();
	
    void Manage();

private:
    Manager();
    virtual ~Manager();
    
    static Manager m_Instance;
};

#endif /* MANAGER_H */

Tệp Manager.cpp:

#include "Manager.h"
#include "stdio.h"

Manager Manager::m_Instance;
Manager* Manager::GetInstance() {
    return &m_Instance;
}

Manager::Manager() {
}

Manager::~Manager() {
}

void Manager::Manage() {
    printf("Function Manage is called");
}

Tệp main.cpp sử dụng lớp Manager:

#include "Manager.h"

int main(int argc, char** argv) {
    Manager* m = Manager::GetInstance();
    m->Manage();
    return 0;
}

Download source code: [[File:SimpleSingleton.zip]]  (Đọc tệp README để biết cách build và chạy).

Ngoài ra bạn có thể sử dụng con trỏ để tiết kiệm vùng nhớ Stack nhưng khi đó bạn cần chú ý:

  • Hàm GetInstance() phải kiểm tra xem biến đã được khởi tạo chưa để thực hiện khởi tạo.
  • Phải thêm hàm DestroyInstance() để giải phóng bộ nhớ.
  • Xác định điểm kết thúc ứng dụng để gọi hàm DestroyInstance()
  • Đặc biệt, nêu Singleton này được sử dụng rong multi-threading, bạn phải sử dụng mutex trong hàm GetInstance. Trường hợp cấp phát tĩnh thì bạn không cần làm đều này.

Code cài đặt lại sử dụng con trỏ như sau:

Tệp Manager.h:

#ifndef MANAGER_H
#define MANAGER_H

class Manager {
public:
    static Manager* GetInstance();
    static void DestroyInstance();
	
    void Manage();

private:
    Manager();
    virtual ~Manager();
    
    static Manager* m_Instance;
};

#endif /* MANAGER_H */

Tệp Manager.cpp:

#include "Manager.h"
#include "stdio.h"

Manager* Manager::m_Instance = NULL;
Manager* Manager::GetInstance() {
    if (m_Instance==NULL) {
	m_Instance = new Manager();
    }
    return m_Instance;
}

void Manager::DestroyInstance() {
    if (m_Instance!=NULL) {
	delete m_Instance;
	m_Instance = NULL;
    }
}

Manager::Manager() {
}

Manager::~Manager() {
}

void Manager::Manage() {
    printf("Function Manage is called");
}

Tệp main.cpp sử dụng lớp Manager:

#include "Manager.h"

int main(int argc, char** argv) {
    Manager* m = Manager::GetInstance();
    m->Manage();
    // ...
    Manager::DestroyInstance();
    return 0;
}

Download source code: [[File:SimpleSingletonPtr.zip]]  (Đọc tệp README để biết cách build và chạy).

Cài đặt Singleton dạng Template

Trường hợp có nhiều lớp cần cài đặt Singleton, nếu cứ mỗi lớp phải thực hiện code như trên thì khá mất công cài đặt và code bị lặp nhiều. Vì thế một giải pháp hiểu quả hơn là sử dụng template để cài đặt Singleton. Trong trường hợp này bạn cần thực hiện như sau:

  • Cài đặt lớp Singleton dạng Template.
  • Các lớp cần cài đặt Singleton chỉ cần kế thừa từ lớp template Singleton.

Chú ý: Trong trường hợp này, để đảm bảo đặc tính của Singleton, các bạn phải sử dụng từ khóa friend trong cả lớp template Singleton và lớp cần triển khai Singleton.

Tệp Singleton.h (Cài đặt ở dạng Template):

#ifndef SINGLETON_H
#define SINGLETON_H

template<typename T>
class Singleton {
public:
    static T* GetInstance() {
	    static T instance;
	    return &instance;
    }

private:
    friend T;
    Singleton() {}
    virtual ~Singleton() {}
};

#endif /* SINGLETON_H */

Tệp Manager.h (Class này kế thừa lại lớp Singleton):

#ifndef MANAGER_H
#define MANAGER_H

#include "Singleton.h"

class Manager : public Singleton<Manager> {
public:
    void Manage();

private:
    friend Singleton<Manager>;
    Manager();
    virtual ~Manager();
};

#endif /* MANAGER_H */

Tệp Manager.cpp (Chỉ cần cài đặt các hàm nghiệp vụ mà không phải cài đặt lại Singleton):

#include "Manager.h"
#include <stdio.h>

Manager::Manager() : Singleton<Manager>() {
}

Manager::~Manager() {
}

void Manager::Manage() {
    printf("Function Manage is called");
}

Tệp main.cpp sử dụng lớp Manager:

#include "Manager.h"

int main(int argc, char** argv) {
    Manager* m = Manager::GetInstance();
    m->Manage();
    return 0;
}

Download source code: [[File:TemplateSingleton.zip]]  (Đọc tệp README để biết cách build và chạy).

Kết hợp Singleton dạng Template với SmartPointer (Tối ưu nhất)

Việc cấp phát tĩnh có nhiều ưu điểm:

  • Cài đặt đơn giản
  • Không phải xác định điểm để thực hiện giải phóng đối tượng như cấp phát động
  • Không phải lo xử lý trong multi-threading

Nhưng có một nhược điểm là tốn vùng nhớ stack nếu đối tượng T có kích thước lớn. Mà mỗi ứng dụng, vùng nhớ stack luôn có giới hạn, nếu dùng quá sẽ gây chết chương trình. Để khắc phục nhược điểm này,  sử dụng thêm đối tượng SmartPointer.

Tệp SmartPointer.h:

#ifndef SMART_POINTER_H
#define SMART_POINTER_H

#include <stdio.h>
#include <exception>

using namespace std;

#define SAFE_NEW(ptr, object)                               { try { ptr = new object; } catch (std::exception ex) { printf("Error when allocating object %s: %s", #object, ex.what()); }; }
#define SAFE_DELETE(ptr)                                    { if ( (ptr) != NULL ) delete (ptr); (ptr) = NULL; }

template <typename T>
class SmartPointer {
private:
    friend T;
    T* m_Pointer;
    bool m_AutoDestroy;
    
    inline void Destroy()
    {
        if (m_AutoDestroy)
        {
            SAFE_DELETE(m_Pointer);
        } else
        {
            m_Pointer = NULL;
        }
        m_AutoDestroy = true;
    }
	
public:
    SmartPointer(T* pointer=NULL, bool autoDestroy=true)
    {
        m_Pointer = NULL;
        m_AutoDestroy = true;

        if (pointer==NULL) {
            SAFE_NEW(m_Pointer, T);
        } else {
            m_Pointer = pointer;
            m_AutoDestroy = autoDestroy;
        }
    }
    
    SmartPointer(SmartPointer& orig)
    {
        m_Pointer = orig.GetPointer();
        m_AutoDestroy = orig.GetAutoDestroy();
        orig.Release();
    }
	
    virtual ~SmartPointer()
    {
        Destroy();
    }
	
    inline T* GetPointer()
    {
        if (m_Pointer==NULL) {
            // This code to ensure pointer is always not null
            SAFE_NEW(m_Pointer, T);
        }
        return m_Pointer;
    }
    
    bool GetAutoDestroy()
    {
        return m_AutoDestroy;
    }
    
    T* Release()
    {
        T* ptr = m_Pointer;
        m_Pointer = NULL;
        m_AutoDestroy = true;
        return ptr;
    }
    
    void Reset()
    {
        Destroy();
    }
    
    SmartPointer& operator=(SmartPointer& orig)
    {
        m_Pointer = orig.GetPointer();
        m_AutoDestroy = orig.GetAutoDestroy();
        orig.Release();
    }
    
    T& operator * (){ return *GetPointer(); }
    T* operator -> (){ return GetPointer(); }
};

#endif /* SMART_POINTER_H */

Tệp Singleton.h:

#ifndef SINGLETON_H
#define SINGLETON_H

#include "SmartPointer.h"

template<typename T>
class Singleton {
public:
    static T* GetInstance() {
	// Using static to support multi-threading
        // If not use static, you should use mutex to ensure multi-threading
        static SmartPointer<T> instance;
        return instance.GetPointer();
    }

private:
    friend T;
    Singleton() {}
    virtual ~Singleton() {}
};

#endif /* SINGLETON_H */

Còn tệp tệp Manager.h, Manager.cpp và main.cpp thì vẫn giữ nguyên như mục 2. 

Download source code: [[File:TemplateSingletonEx.zip]] (Đọc tệp README để biết cách build và chạy).