赞
踩
单例模式要求一个类在一个进程中只能创建一个对象。比如 cyberrt 中的 TimingWheel 类就是单例模式,这个类管理着一个进程内的所有定时器,只需要一个对象就可以。
单例模式的实现有两种方式,懒汉式和饿汉式。懒汉式,当第一次使用的时候才会真正创建这个对象;饿汉式,不管会不会用到这个对象,在进程启动的时候都会创建这个对象,如果一直不使用,那么就会造成资源浪费。饿汉式的缺点是可能造成资源浪费,但是对性能友好,因为在进程启动的时候就直接创建了,需要使用的时候可以直接拿来使用;懒汉式反之。
在工作中一般使用懒汉式。
懒汉式示例代码如下,在如下代码中实现了自动回收的机制,通过内部的类 Recycler 来完成。
- #include <iostream>
- #include <mutex>
-
- class Test {
- public:
- static Test *GetInstance() {
- std::lock_guard<std::mutex> lock(mtx);
- if (instance == nullptr) {
- instance = new Test();
- return instance;
- }
- return instance;
- };
-
- Test(const Test &) = delete;
- Test &operator=(const Test &) = delete;
-
- ~Test() {
- std::cout << "~Test()" << std::endl;
- };
-
- class Recycler {
- public:
- ~Recycler() {
- if (Test::instance) {
- delete Test::instance;
- } else {
- std::cout << "no need to recycle" << std::endl;
- }
- }
- };
- static Recycler recycler;
-
- void Do() {
- std::cout << "Do()" << std::endl;
- }
-
- private:
- static Test *instance;
- static std::mutex mtx;
- Test() {
- std::cout << "Test()" << std::endl;
- };
- };
-
- Test *Test::instance = nullptr;
- std::mutex Test::mtx;
- Test::Recycler recycler;
-
- void TestDo(Test test) {
- test.Do();
- }
-
- int main() {
- Test *test = Test::GetInstance();
- test->Do();
- return 0;
- }
特点:
(1)第一次使用对象的时候才会创建,懒加载模式。懒加载思想很常见,比如 linux 中用户态的内存管理,就是典型的懒加载。
(2)在 GetInstance() 需要加锁,如果多线程频繁调用,会影响性能。个人认为这个只是理论上的缺点,真正使用中,单例模式很少有多线程频繁调用的情况。
注意点:
(1)在 GetInstance() 中需要加锁。
(2)如下两个静态成员变量需要在类的外部初始化
类的静态变量需要在类外部初始化,这是静态变量和非静态变量的明显区别。
static Test *instance;
static std::mutex mtx;
(3)拷贝构造函数和赋值运算符需要禁用
如果不禁用,通过拷贝构造函数和赋值运算符可以生成新的对象,就不能保证单例了。
不管将来用不用,这个对象都会创建好。
- #include <iostream>
- #include <mutex>
-
- class Test {
- public:
- static Test *GetInstance() {
- return instance;
- };
-
- Test(const Test &) = delete;
- Test &operator=(const Test &) = delete;
-
- ~Test() {
- std::cout << "~Test()" << std::endl;
- };
-
- class Recycler {
- public:
- ~Recycler() {
- if (Test::instance) {
- delete Test::instance;
- } else {
- std::cout << "no need to recycle" << std::endl;
- }
- }
- };
- static Recycler recycler;
-
- void Do() {
- std::cout << "Do()" << std::endl;
- }
-
- private:
- static Test *instance;
- Test() {
- std::cout << "Test()" << std::endl;
- };
- };
-
- Test *Test::instance = new Test();
- Test::Recycler recycler;
-
- char *p = (char *)malloc(1024);
-
- int main() {
- printf("main start\n");
- Test *test = Test::GetInstance();
- test->Do();
- printf("p: %p\n", p);
- p[0] = 1;
- return 0;
- }
题外话:
从上边的代码实现中可以看出来,在 c++ 中,在函数外部是可以调用 new 来创建对象的,这种使用方式是自己很少使用的。
并且在函数外部也可以是有 malloc() 来申请内存。
但是在 c 中,在函数外部申请内存的话,如下代码所示,编译会报错。
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
-
- const char *p = (char *)malloc(1024);
- int main() {
- printf("p: %p\n", p);
- p[0] = 1;
- return 0;
- }
cyberrt 中的类 TimingWheel 使用了单例模式。TimingWheel 是一个进程内所有定时器的底层管理者。cyberrt 中实现单例的方式封装在了一个宏里边,这个宏是 DECLARE_SINGLETON,定义如下,实现主要有以下几点。
(1)使用 std::call_once 来实现,保证了原子性
(2)禁用了拷贝构造函数和赋值构造函数
- #ifndef DISALLOW_COPY_AND_ASSIGN
- #define DISALLOW_COPY_AND_ASSIGN(classname) \
- classname(const classname &) = delete; \
- classname &operator=(const classname &) = delete;
- #endif
-
- #ifndef DECLARE_SINGLETON
- #define DECLARE_SINGLETON(classname) \
- public: \
- static classname *instance(bool create_if_needed = true) { \
- static classname *inst = nullptr; \
- if (!inst && create_if_needed) { \
- static std::once_flag flag; \
- std::call_once(flag, [&] { inst = new (std::nothrow) classname(); }); \
- } \
- return inst; \
- } \
- \
- static void clean_up() { \
- auto inst = instance(false); \
- if (inst != nullptr) { \
- call_shut_down(inst); \
- } \
- } \
- \
- private: \
- classname(); \
- DISALLOW_COPY_AND_ASSIGN(classname)
- #endif
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。