单件模式也称为单例模式或者单态模式,这是一种创建型模式,创建只能产生一个对象实例的类。
只能创建一个属于该类对象的类就称为单件类。
也许有人好奇,只能创建一个对象的类,那么我在程序当中只创建一个该对象不就行了吗?这个类还需要设计吗?是的,但是著名c++界的Scott Meyers说过"要使接口或者类型易于正确使用,难以错误使用",,也就是说只能创建一个属于该类对象的类的工作应该要交给类的设计者去实现这个事情,而不是把这个工作交给类的使用者去考虑这个事情。
单例模式的样式有很多,本篇博文就这些范例来个总结吧。
范例一:
#include <iostream>
#include <list>
#include <map>
using namespace std;
class config
{
private:
config() {
}
~config(){
}
config(const config &){
}
config& operator = (const config&) {
return *this; }
public:
static config* getInstance()
{
if(pc == nullptr)
{
pc = new config();
}
return pc;
}
private:
static config* pc;
};
/*静态变量类外初始化*/
config* config::pc = nullptr;
int main()
{
/*检测内存泄漏*/
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
/*构造函数是私有的,以下的方式创建一个对象不可行
config p;
config* p = new config();
*/
/*只能通过调用getInstance函数的方式创建一个对象*/
config* p = config::getInstance();
return 0;
}
该范例通过把类的构造函数封装为私有的,这样就无法在堆和栈上创建一个属于该类的对象了,只能通过调用静态成员函数getInstance来生成对象,这样,下次在通过getInstance函数生成这个对象的时候,这个对象还是之前的那个,这样就达到了我们的目的,既保证了这个类只会创建出一个属于该类的对象出来。
上面的范例当中如果在单线程使用当然是ok的,但是如果在多线程当中使用,可能就会存在问题了。举一个例子,比如有两个线程A和B,,当线程A执行到pc = new config();这一行的时候,由于线程切换的原因,且换到B去了,这个时候线程B,new了一个config对象出来,这个时候再次由于线程切换,切换回线程A当中,那么线程A再次执行了new 一个config这个对象出来,这样就与我们的设计不符合了,所以这样的设计显然是存在缺陷的。
范例二:
#include <iostream>
#include <list>
#include <map>
#include <mutex>
using namespace std;
class config
{
private:
config() {
}
~config(){
}
config(const config &){
}
config& operator = (const config&) {
return *this; }
public:
static config* getInstance()
{
mymutex.lock();
if(pc == nullptr)
{
pc = new config();
}
mymutex.unlock();
return pc;
}
private:
static std::mutex mymutex;
static config* pc;
};
/*静态变量类外初始化*/
config* config::pc = nullptr;
std::mutex config::mymutex;
int main()
{
/*检测内存泄漏*/
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
/*构造函数是私有的,以下的方式创建一个对象不可行
config p;
config* p = new config();
*/
/*只能通过调用getInstance函数的方式创建一个对象*/
config* p = config::getInstance();
return 0;
}
范例二通过加锁的方式避免了范例一出现的问题,但是,如果程序当中存在大量的config::getInstance这个函数的情形时,显然频繁加锁会导致效率低效的问题,大多数的时候我们只是把new出来的对象当做一个可读对象去处理,显然是不必要频繁加锁的。
范例三:
#include <iostream>
#include <list>
#include <map>
#include <mutex>
using namespace std;
class config
{
private:
config() {
}
~config(){
}
config(const config &){
}
config& operator = (const config&) {
return *this; }
public:
static config* getInstance()
{
if(pc == nullptr)
{
mymutex.lock();
if(pc == nullptr)
{
pc = new config();
}
mymutex.unlock();
}
return pc;
}
private:
static std::mutex mymutex;
static config* pc;
};
std::mutex config::mymutex;
/*静态变量类外初始化*/
config* config::pc = nullptr;
int main()
{
/*检测内存泄漏*/
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
/*构造函数是私有的,以下的方式创建一个对象不可行
config p;
config* p = new config();
*/
/*只能通过调用getInstance函数的方式创建一个对象*/
config* p = config::getInstance();
return 0;
}
采用双重锁定,避免一开始就先上锁。但是采用双层锁定的话,依然是存在问题的,我们知道new这个动作是包含几个步骤的,比如,先分配内存,然后调用构造函数,最后让这个指针指向这块内存等等。
以范例三来说,比如有两个线程A和B,,当线程A执行到pc = new config();这一行的时候,可能只是刚刚指向了,分配内存这个动作,然后由于线程切换的原因,且换到B去了,这个时候线程B,去判断pc是否为空,显然不为空,但是可能由于没调用构造函数进行初始化,然后直接就return pc回去了,这个时候在线程B当中使用这个内存显然也是存在风险的,因为我还没调用构造函数进行初始化呢。
一般来说,解决这种多线程带来的问题,我们一开始先调用getInstance函数而后在创建多个线程,显然这样是可以解决上面的范例二和范例三带来的问题的。
这里在提供一个范例解决双重锁定带来的问题,不过这个范例我没有进行具体的测试,有兴趣的同学可以看看。
class Config
{
private:
Config() {
};
Config(const Config& tmpobj);
Config& operator = (const Config& tmpobj);
~Config() {
};
public:
static Config* getInstance()
{
Config* tmp = m_instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);
if (tmp == nullptr)
{
std::lock_guard<std::mutex> lock(m_mutex);
tmp = m_instance.load(std::memory_order_relaxed);
if (tmp == nullptr)
{
tmp = new GameConfig();
std::atomic_thread_fence(std::memory_order_release);
m_instance.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}
private:
static atomic<GameConfig*> m_instance;
static std::mutex m_mutex;
};
std::atomic<GameConfig*> GameConfig::m_instance;
std::mutex Config::m_mutex;
范例4:我们在来看看其它解决办法,还有一些概念,比如饿汉式和懒汉式。
懒汉式:(很懒惰之意),程序执行后该单件类对象并不存在,只有第一次调用getInstance成员函数时该单件类对象才被创建,比如上面的范例一,范例二,范例三。
饿汉式:(很饥渴很迫切之意)程序一致性,不管是否调用了getInstance成员函数,这个单件类就已经被创建了(对象创建不受多线程问题困扰)。但是,这个方式也不利于我们控制这个对象的生成时机,各有利弊吧。
比如如下范例:
#include <iostream>
#include <list>
#include <map>
#include <mutex>
using namespace std;
class config
{
private:
config() {
}
~config(){
}
config(const config &){
}
config& operator = (const config&) {
return *this; }
private:
static config* pc;
};
/*静态变量类外初始化*/
config* config::pc = new config();
int main()
{
/*检测内存泄漏*/
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
return 0;
}
静态成员变量直接在类外初始化时就new一个对象出来,这个动作是在main函数之前执行的,所以,显示可以避免多线程带来的问题。
范例五:我们再来谈谈单例模式的内存释放问题。
对于内存释放这一块的话,其实在程序结束时,有操作系统回收问题也不大,但是如果是希望我们手动的去释放的话,我们就设计一下吧。
比如这种手动的释放,也属于懒汉式的方式,需要我们手动的调用freeInstance函数。
#include <iostream>
#include <list>
#include <map>
#include <mutex>
using namespace std;
class config
{
private:
config() {
}
~config(){
}
config(const config &){
}
config& operator = (const config&) {
return *this; }
public:
static config* getInstance()
{
return pc;
}
static void freeInstance()
{
if(pc != nullptr)
{
delete pc;
pc = nullptr;
}
}
private:
static config* pc;
};
/*静态变量类外初始化*/
config* config::pc = nullptr;
int main()
{
/*检测内存泄漏*/
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
/*构造函数是私有的,以下的方式创建一个对象不可行
config p;
config* p = new config();
*/
/*只能通过调用getInstance函数的方式创建一个对象*/
config* p = config::getInstance();
p->freeInstance();
return 0;
}
我们再来看看另一种方式,饿汉式的释放,不需要借助于我们手动的调用释放函数。
#include <iostream>
#include <list>
#include <map>
#include <mutex>
using namespace std;
class config
{
private:
config() {
}
~config(){
}
config(const config &){
}
config& operator = (const config&) {
return *this; }
private:
//手工释放单件类对象引入的GameConfig类中的嵌套类(垃圾回收)
class Garbo
{
public:
~Garbo()
{
if(pc != nullptr)
{
delete pc;
pc = nullptr;
}
}
};
private:
static Garbo garboobj;
static config* pc;
};
/*静态变量类外初始化*/
config* config::pc = new config();
config::Garbo config::garboobj;
int main()
{
/*检测内存泄漏*/
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
return 0;
}
最后举一两个实际工作当中用到单例模式的例子。
http上报的
#ifndef _HTTP_H
#define _HTTP_H
#include <iostream>
extern FILE* Templog;
/*使用单例模式*/
class CHttpPackage
{
private:
CHttpPackage()
{
bool ret = false;
ret = init();
if (ret == false)
{
fprintf(Templog, "ERROR-> FUNCTION = %s ,LINE = %d\n", __FUNCTION__, __LINE__);
fflush(Templog);
}
}
~CHttpPackage() = default;
CHttpPackage(const CHttpPackage&) = delete;
CHttpPackage& operator= (const CHttpPackage&) = delete;
public:
static CHttpPackage* getInstance();
static void FreeInstance();
static size_t OnWriteData(void* buffer, size_t size, size_t nmemb, void* lpVoid);
public:
bool init();
int Post(const std::string& strUrl, const std::string& strData);
private:
/*声明一个静态成员变量*/
static CHttpPackage *_M_instance;
};
/*宏定义一个getInstance实例,允许客户访问它的唯一实例,getInstance是一个静态方法*/
#define g_httpPackage() CHttpPackage::getInstance()
/*函数流程结束时释放该内存*/
#define Free_g_httpPackage() CHttpPackage::FreeInstance()
/*使用单例模式步骤:
(1)构造函数私有化
(2)提供一个全局的静态方法
(3)在类定义中定义静态指针,指向本类的静态成员变量指针
*/
#endif // !_HTTP_H
#include "http.h"
#include <curl/curl.h>
#include <memory>
extern FILE* Templog;
/*静态成员变量的类外定义 -> 饿汉式,提前创建对象,在多线程当中依然是安全的*/
CHttpPackage *CHttpPackage::_M_instance = new CHttpPackage();
/***************************************************************
* @file http.cpp
* @brief curl初始化
* @author txj
* @version v1
* @date 2021/5/28
**************************************************************/
bool CHttpPackage::init()
{
/*初始化所有可能的调用*/
int ret_code = curl_global_init(CURL_GLOBAL_ALL);
if (ret_code != CURLE_OK)
{
fprintf(Templog, "ERROR-> FUNCTION = %s ,LINE = %d\n", __FUNCTION__, __LINE__);
fflush(Templog);
return false;
}
return true;
}
/***************************************************************
* @file http.cpp
* @brief 获取post返回的数据并将其写入文件当中
* @author txj
* @version v1
* @date 2021/5/28
**************************************************************/
size_t CHttpPackage::OnWriteData(void* buffer, size_t size, size_t nmemb, void* lpVoid)
{
FILE* fp = (FILE*)lpVoid;
if(NULL == buffer)
{
fprintf(Templog, "ERROR-> FUNCTION = %s ,LINE = %d\n", __FUNCTION__, __LINE__);
fflush(Templog);
return -1;
}
size_t ret_size = fwrite(buffer, size, nmemb, fp);
fprintf(Templog, "\n");
fflush(Templog);
return ret_size;
}
/***************************************************************
* @file http.cpp
* @brief 生成一个单实例
* @author txj
* @version v1
* @date 2021/5/28
**************************************************************/
CHttpPackage *CHttpPackage::getInstance()
{
if (_M_instance == nullptr)
{
fprintf(Templog, "ERROR-> FUNCTION = %s ,LINE = %d\n", __FUNCTION__, __LINE__);
fflush(Templog);
return _M_instance;
}
else
{
return _M_instance;
}
}
/***************************************************************
* @file http.cpp
* @brief 释放内存
* @author txj
* @version v1
* @date 2021/5/28
**************************************************************/
void CHttpPackage::FreeInstance()
{
if (_M_instance != nullptr)
{
delete _M_instance;
_M_instance = nullptr;
}
}
/***************************************************************
* @file http.cpp
* @brief http post(get)方式上传数据
* @author txj
* @version v1
* @date 2021/5/28
**************************************************************/
int CHttpPackage::Post(const std::string& strUrl, const std::string& strData)
{
CURLcode res;
/*初始化curl,获取一个curl对象,类似于返回一个FILE类型的指针*/
CURL* curl = curl_easy_init();
if (NULL == curl)
{
fprintf(Templog, "ERROR-> FUNCTION = %s ,LINE = %d\n", __FUNCTION__, __LINE__);
fflush(Templog);
return CURLE_FAILED_INIT;
}
/*设置请求头信息*/
curl_slist* http_header = NULL;
http_header = curl_slist_append(http_header, "sign:1");
if (http_header)
{
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, http_header);
}
else
{
fprintf(Templog, "ERROR-> FUNCTION = %s ,LINE = %d\n", __FUNCTION__, __LINE__);
fflush(Templog);
}
/*使用curl_easy_setopt函数设置传输选项
第三个参数怎么用取决于第二个参数*/
/*请求的地址,访问特定的URL*/
curl_easy_setopt(curl, CURLOPT_URL, strUrl.c_str());
/*设置POST,默认为0,设置为一表示发起一次post请求*/
curl_easy_setopt(curl, CURLOPT_POST, 1);
/*设置参数*/
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, strData.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strData.size());
curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL);
/*写入数据的回调*/
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, OnWriteData);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, Templog);
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
/*设置传输时间*/
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5000);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5000);
curl_easy_setopt(curl, CURLOPT_DNS_CACHE_TIMEOUT, 5000);
/*执行查询操作,是否完成传输任务*/
res = curl_easy_perform(curl);
if (res != CURLE_OK)
{
/*获取详细错误信息*/
fprintf(Templog, "ERROR-> FUNCTION = %s ,LINE = %d,curl_easy_perform() failed: %s\n",
__FUNCTION__, __LINE__, curl_easy_strerror(res));
fflush(Templog);
Free_g_httpPackage();/*释放内存*/
}
/*释放内存,类似于close函数*/
curl_easy_cleanup(curl);
/*释放表头*/
curl_slist_free_all(http_header);
return 0;
}
读配置文件的
#ifndef __NGX_CONF_H__
#define __NGX_CONF_H__
#include <vector>
#include "ngx_global.h"
class CConfig
{
private:
CConfig();
public:
~CConfig();
private:
static CConfig *m_instance;
public:
static CConfig* GetInstance()
{
if(m_instance == NULL)
{
if(m_instance == NULL)
{
m_instance = new CConfig();
static CGarhuishou cl;
}
}
return m_instance;
}
class CGarhuishou
{
public:
~CGarhuishou()
{
if (CConfig::m_instance)
{
delete CConfig::m_instance;
CConfig::m_instance = NULL;
}
}
};
public:
bool Load(const char *pconfName); //装载配置文件
const char *GetString(const char *p_itemname);
int GetIntDefault(const char *p_itemname,const int def);
public:
std::vector<LPCConfItem> m_ConfigItemList; //存储配置信息的列表
};
#endif