什么是单例模式
单例模式是一种设计模式,它保证一个类只有一个实例,并提供一个全局访问点来访问该实例。这个模式通常用于控制资源的访问,例如数据库连接、线程池等。单例模式通过限制实例化操作并提供访问方法,确保在整个应用程序中只存在一个实例,避免了多个实例之间的冲突和资源浪费。
在单例模式中,类的构造函数被私有化,使得外部无法直接实例化该类。同时,在类的内部创建一个私有静态变量来保存唯一的实例,然后提供一个静态方法来访问该实例。这个静态方法会检查实例是否已经存在,如果存在则返回原有实例,否则创建一个新的实例并返回。这样就可以保证在任何时候,整个应用程序中只有一个实例存在。
需要注意的是,单例模式不是线程安全的。在多线程环境下,可能会出现多个线程同时访问单例对象的情况。为了解决这个问题,可以使用双重检查锁定等技术来确保线程安全。
单例模式一般有两种创建方法:
1、饿汉式
2、懒汉式
这两种的含义后续在作说明。
代码实现–懒汉式
- jk.cpp
#include <iostream>
class jk
{
private:
jk(){ std::cout << "i am jk" << std::endl; }
~jk() { std::cout << "i am ~ jk" << std::endl; }
jk(jk&) = delete;
jk& operator=(const jk&) = delete;
public:
static jk *GetjkPoint()
{
if (ptr == nullptr)
{
std::cout << "jk init ptr is nullptr" << std::endl;
ptr = new jk();
}
return ptr;
}
public:
void out() { std::cout << "hello world!" << std::endl; }
private:
static jk *ptr;
};
jk * jk::ptr = nullptr;
int main()
{
jk::GetjkPoint()->out();
return 0;
}
编译运行
g++ jk.cpp -std=c++11 -o jk
./jk
运行结果
jk init ptr is nullptr
i am jk
hello world
细心的就会发现上诉代码有如下问题
线程安全的问题,当多线程获取单例时有可能引发竞态条件:第一个线程在if中判断 ptr是空的,于是开始实例化单例;同时第2个线程也尝试获取单例,这个时候判断ptr还是空的,于是也开始实例化单例;这样就会实例化出两个对象,这就是线程安全问题的由来; 解决办法:加锁
内存泄漏. 注意到类中只负责new出对象,却没有负责delete对象因此只有构造函数被调用,析构函数却没有被调用;因此会导致内存泄漏。解决办法1:当然我们自己手动调用delete来进行释放是可以的,但是维护在何处释放又成了问题。
我们可以做如下优化:
#include <iostream>
class jk
{
private:
jk(){ std::cout << "i am jk" << std::endl; }
~jk() { std::cout << "i am ~ jk" << std::endl; }
jk(jk&) = delete;
jk& operator=(const jk&) = delete;
public:
static jk *GetjkPoint()
{
if (ptr == nullptr)
{
std::cout << "jk init ptr is nullptr" << std::endl;
ptr = new jk();
static garbo garboobj;
}
return ptr;
}
public:
void out() { std::cout << "hello world!" << std::endl; }
private:
class garbo
{
public:
~garbo()
{
if(ptr != nullptr)
{
delete ptr;
ptr = nullptr;
}
}
};
private:
static jk *ptr;
};
jk * jk::ptr = nullptr;
int main()
{
jk::GetjkPoint()->out();
return 0;
}
输出结果:
jk init ptr is nullptr
i am jk
hello world!
i am ~ jk
我们还可以使用智能指针实现如下优化
#include <iostream>
#include <memory> // C++11 shared_ptr头文件
#include <mutex> // C++11 mutex头文件
class jk
{
private:
jk() { std::cout << "i am jk" << std::endl; }
// ~jk() { std::cout << "i am ~ jk" << std::endl; }
jk(jk &) = delete;
jk &operator=(const jk &) = delete;
public:
~jk() { std::cout << "i am ~ jk" << std::endl; }
public:
static std::shared_ptr<jk> GetjkPoint()
{
if (ptr == nullptr)
{
std::lock_guard<std::mutex> lk(m_tex);
std::cout << "jk init ptr is nullptr" << std::endl;
if(ptr == nullptr)
{
// ptr = std::make_shared<jk>();
ptr = std::shared_ptr<jk>(new jk());
}
}
return ptr;
}
public:
void out() { std::cout << "hello world!" << std::endl; }
private:
static std::shared_ptr<jk> ptr;
static std::mutex m_tex;
};
std::shared_ptr<jk> jk::ptr = nullptr;
std::mutex jk::m_tex;
int main()
{
jk::GetjkPoint()->out();
return 0;
}
运行可以发现:
jk init ptr is nullptr
i am jk
hello world!
i am ~ jk
析构已经可以跑到了。
代码实现–饿汉式
#include <iostream>
class jk
{
private:
jk(){ std::cout << "i am jk" << std::endl; }
~jk() { std::cout << "i am ~ jk" << std::endl; }
jk(jk&) = delete;
jk& operator=(const jk&) = delete;
public:
static jk *GetjkPoint()
{
return ptr;
}
public:
void out() { std::cout << "hello world!" << std::endl; }
private:
class garbo
{
public:
~garbo()
{
if(ptr != nullptr)
{
delete ptr;
ptr = nullptr;
}
}
};
private:
static jk *ptr;
static garbo garboobj;
};
jk * jk::ptr = new jk();
jk::garbo jk::garboobj;
int main()
{
jk::GetjkPoint()->out();
return 0;
}
运行结果:
i am jk
hello world!
i am ~ jk
看完上述代码会发现:
饿汉式 :程序一执行,不管是否被调用,先new出来。这种创建方法不受多线程的困扰。缺点也很明显
必须在jk * jk::ptr = new jk();
之后才能使用静态类中的方法(jk::GetjkPoint()->out();
)。否则ptr的值是nullptr,程序直接挂了。
懒汉式:使用时才会被new出来,但是要考虑多线程问题。
最后
以上这些方法还是比较繁琐的,最安全最简洁的方法如下。
#include <iostream>
class jk
{
private:
jk() { std::cout << "i am jk" << std::endl; }
// ~jk() { std::cout << "i am ~ jk" << std::endl; }
jk(jk &) = delete;
jk &operator=(const jk &) = delete;
public:
~jk() { std::cout << "i am ~ jk" << std::endl; }
public:
static jk& GetjkPoint()
{
static jk ptr;
return ptr;
}
public:
void out() { std::cout << "hello world!" << std::endl; }
};
int main()
{
jk::GetjkPoint().out();
jk::GetjkPoint().out();
return 0;
}
运行结果:
i am jk
hello world!
hello world!
i am ~ jk
总结不易,如果可以 ^ _ ^
别忘了带赞收藏哦!
您的肯定,是我持续更下去的动力。哒咩~~