1. 一般singleton写法
单例模式即要求只有在第一次调用的时候创建该对象,主要分为以下两条路(返回指针还是引用),返回引用可以防止使用中delete instance导致对象被提前销毁:
- private包含static指针以及构造函数,public里创建函数,通过调用其构造函数,返回该指针
- private包含static指针以及构造函数,public里创建函数,通过调用其构造函数,返回该引用
- private包含构造函数,public里创建函数,该函数里设置static变量实例,返回其引用
实际写单例的时候,需要考虑两个问题
- 是否需要delete,如何delete
- 是否考虑竞争
如果使用一般的创建函数,new一个对象,则需要考虑如何delete,否则会出现内存泄漏,这里也可以使用智能指针,但是会相对麻烦,且更耗资源。本文暂不考虑。
懒汉模式(不考虑多线程,且不考虑内存泄漏,没有返回引用)
class singleton //实现单例模式的类
{
private:
singleton(){} //私有的构造函数
static singleton* instance;
public:
static singleton* GetInstance()
{
if (instance== NULL) //判断是否第一调用
instance= new singleton();
return instance;
}
};
下面两个只调用了一次构造函数
singleton* instance_1 = singleton::GetInstance();
singleton* instance_2 = singleton::GetInstance();
懒汉模式(多线程),增加一层instance== NULL判断,以及Lock()
if (instance== NULL) //判断是否第一调用
{
Lock(); //表示上锁的函数
if (instance== NULL)
{
instance= new singleton();
}
UnLock() //解锁函数
}
2. muduo::singleton
muduo里singleton主要做了以下动作
- 返回&
- 使用atexit,在main函数返回或者exit时,完成delete调用,完成栈清理
- 使用pthread_once设置PTHREAD_ONCE_INIT,只在第一次调用new,避免多线程竞争,多次new
其暴露了instance函数,因为其返回引用,可以对T实例对象修改,但保证只有一个对象
2.1 singleton代码
template<typename T>
class Singleton : noncopyable
{
public:
Singleton() = delete;
~Singleton() = delete;
static T& instance()
{
pthread_once(&ponce_, &Singleton::init);
assert(value_ != NULL);
return *value_;
}
private:
static void init()
{
value_ = new T();
if (!detail::has_no_destroy<T>::value)
{
::atexit(destroy);
}
}
static void destroy()
{
typedef char T_must_be_complete_type[sizeof(T) == 0 ? -1 : 1];
T_must_be_complete_type dummy; (void) dummy;
delete value_;
value_ = NULL;
}
private:
static pthread_once_t ponce_;
static T* value_;
};
template<typename T>
pthread_once_t Singleton<T>::ponce_ = PTHREAD_ONCE_INIT;
template<typename T>
T* Singleton<T>::value_ = NULL;
2.2 muduo 测试代码
#include "muduo/base/Singleton.h"
#include "muduo/base/CurrentThread.h"
#include "muduo/base/Thread.h"
#include <stdio.h>
class Test : muduo::noncopyable
{
public:
Test()
{
printf("tid=%d, constructing %p\n", muduo::CurrentThread::tid(), this);
}
~Test()
{
printf("tid=%d, destructing %p %s\n", muduo::CurrentThread::tid(), this, name_.c_str());
}
const muduo::string& name() const { return name_; }
void setName(const muduo::string& n) { name_ = n; }
private:
muduo::string name_;
};
class TestNoDestroy : muduo::noncopyable
{
public:
// Tag member for Singleton<T>
void no_destroy();
TestNoDestroy()
{
printf("tid=%d, constructing TestNoDestroy %p\n", muduo::CurrentThread::tid(), this);
}
~TestNoDestroy()
{
printf("tid=%d, destructing TestNoDestroy %p\n", muduo::CurrentThread::tid(), this);
}
};
void threadFunc()
{
printf("tid=%d, %p name=%s\n",
muduo::CurrentThread::tid(),
&muduo::Singleton<Test>::instance(),
muduo::Singleton<Test>::instance().name().c_str());
muduo::Singleton<Test>::instance().setName("only one, changed");
}
int main()
{
muduo::Singleton<Test>::instance().setName("only one");
muduo::Thread t1(threadFunc);
t1.start();
t1.join();
printf("tid=%d, %p name=%s\n",
muduo::CurrentThread::tid(),
&muduo::Singleton<Test>::instance(),
muduo::Singleton<Test>::instance().name().c_str());
muduo::Singleton<TestNoDestroy>::instance();
printf("with valgrind, you should see %zd-byte memory leak.\n", sizeof(TestNoDestroy));
}
2.3 ThreadLocalSingleton
分析其代码:
muduo里ThreadLocalSingleton有如下特点
- 使用!t_value_判断是否null
- 在pthread_key_create里,注册destructor,线程结束时调用,完成栈清理
- 返回&
- 保证只有一个ThreadLocal对象(否则在主线程中可以创建多个TSD对象,即多个key指针)
namespace muduo
{
template<typename T>
class ThreadLocalSingleton : noncopyable
{
public:
ThreadLocalSingleton() = delete;
~ThreadLocalSingleton() = delete;
static T& instance()
{
if (!t_value_)
{
t_value_ = new T();
deleter_.set(t_value_);
}
return *t_value_;
}
static T* pointer()
{
return t_value_;
}
private:
static void destructor(void* obj)
{
assert(obj == t_value_);
typedef char T_must_be_complete_type[sizeof(T) == 0 ? -1 : 1];
T_must_be_complete_type dummy; (void) dummy;
delete t_value_;
t_value_ = 0;
}
class Deleter
{
public:
Deleter()
{
pthread_key_create(&pkey_, &ThreadLocalSingleton::destructor);
}
~Deleter()
{
pthread_key_delete(pkey_);
}
void set(T* newObj)
{
assert(pthread_getspecific(pkey_) == NULL);
pthread_setspecific(pkey_, newObj);
}
pthread_key_t pkey_;
};
static __thread T* t_value_;
static Deleter deleter_;
};
template<typename T>
__thread T* ThreadLocalSingleton<T>::t_value_ = 0;
template<typename T>
typename ThreadLocalSingleton<T>::Deleter ThreadLocalSingleton<T>::deleter_;
} // namespace muduo
2.2 pthread_once
pthread_once()调用会出现在多个线程中,init_routine()函数仅执行一次,究竟在哪个线程中执行是不定的,是由内核调度来决定
int pthread_once(pthread_once_t *once_control, void (*init_routine) (void));
使用初值为PTHREAD_ONCE_INIT的once_control变量保证init_routine()函数在本进程执行序列中仅执行一次
2.3 atexit的使用
值得注意的点在于atexit函数的调用,其在exit(3)或者main函数结束的时候,都会被调用,回顾下进程正常终止的五个情况,main函数返回等同于exit(3),调用多次相当于进行函数的压栈操作,先入后出。
文章链接
C++ 单例模式总结与剖析