单例模式模板

//单例模板
template <typename T>
class Singleton 
{
    //使用默认构造和析构函数
    Singleton() = default;
    ~Singleton() = default;
public:
	//删除默认的移动、拷贝、赋值、取址
    Singleton(const Singleton &) = delete;
    Singleton& operator=(const Singleton &) = delete;

    Singleton(Singleton &&) = delete;
    Singleton& operator=(Singleton &&) = delete;
    
    T *operator &() = delete;
    
    static T* instance()
    {
        static T object;
        return &object;
    }
};

使用示例:

//测试类
class Test
{
    int id;
public:
    void setId(int id)
    {
        this->id = id;
    }
    int getId() const
    {
        return this->id;
    }
};

int main()
{
    Singleton<Test>::instance()->setId(5);
    std::cout << Singleton<Test>::instance()->getId() << std::endl;
}

如果使用的时候觉得调用太长,可以将其 #define 一下,如:

#define SingletonTest Singleton<Test>::instance()

int main()
{
    SingletonTest->setId(5);
    std::cout << SingletonTest->getId() << std::endl;
}

到这里你可能会提出质疑,我们在此处并没有做到禁止创建 Test 用户类的对象,如果你有这样的需求,那请往下看
为了禁止擅自创建 Test(用户类对象),我们改变下策略:
放开模板限制,在此不再删除取址操作(会影响返回对象的引用),取消模板类构造函数的私有化,作为基类,析构函数标记为virtual;

//单例模式基类,CRTP模板
template <typename T>
class Singleton
{
public:
    Singleton() = default;
    virtual ~Singleton() = default;
    Singleton(const Singleton &) = delete;
    Singleton& operator=(const Singleton &) = delete;

    Singleton(Singleton &&) = delete;
    Singleton& operator=(Singleton &&) = delete;

    static T* instance()
    {
        static T object;
        return &object;
    }
};

用户类继承单例模板
与之前不同的是,将创建对象的限制放在了用户类(即将构造函数设置为私有),构造函数私有化后,单例模板也无法创建对象,于是,将单例模板作为友元类, 模板类才能访问到用户类的构造函数

class Test : public Singleton<Test>
{
public:
    void setId(int id)
    {
        this->id = id;
    }
    int getId() const
    {
        return this->id;
    }
private:
    int id;
    Test() = default;
    
    //单例模板作为友元类
    friend class Singleton<Test>;
};

使用示例:

int main()
{
    //Test t;  //错误,禁止创建对象
    Test::instance()->setId(5);
    std::cout << Test::instance()->getId();
}

如果这里的 Test类构造函数带有参数,那么应该怎么做?
我们可以考虑将 instance()函数,写成模板函数,

//单例模式基类,CRTP模板
template <typename T>
class Singleton
{
public:
    Singleton() = default;
    virtual ~Singleton() = default;
    Singleton(const Singleton &) = delete;
    Singleton& operator=(const Singleton &) = delete;

    Singleton(Singleton &&) = delete;
    Singleton& operator=(Singleton &&) = delete;

    template <typename ...Args>
    static T* instance(Args&&... args)
    {
        static T object(std::forward<Args>(args)...);
        return &object;
    }
};

那么需要给Test类的构造函数传入一个参数,

private:
    Test(QString args)
    {
        qDebug() << args;
    }

使用:

    Singleton<Test>::instance("1111")->triggle();
    Singleton<Test>::instance("2222")->triggle();
    //在这里其实可以看到, 下面一行的参数其实是没有传入 Test类的构造函数的, 两次拿到的Test对象是同一个

但是如此一来的话,每次使用Test单例的时候,就需要传递参数,而我希望仅仅在第一次创建Test类对象时传递参数,在后续使用时不需要传递参数,那么又该怎样做呢?

当然,我们可以定义一个宏,然后在其他所有需要使用这个Test单例对象的地方用这个宏定义即可;

#define SingletonTest Singleton<Test>::instance("1111")

也可以从代码上解决这个问题:
我们可以再将Singleton模板类稍作改动,增加一个 非模板函数类型的instance()重载函数,来解决每次调用都需要传参数的问题;

template <typename T>
class Singleton
{
public:
    Singleton() = default;
    virtual ~Singleton() = default;
    Singleton(const Singleton &) = delete;
    Singleton& operator=(const Singleton &) = delete;

    Singleton(Singleton &&) = delete;
    Singleton& operator=(Singleton &&) = delete;

    template <typename ...Args>
    static T* instance(Args&&... args)
    {
        if (m_ins== nullptr)
        {
            static std::mutex mu;
            std::lock_guard<std::mutex> lock(mu);
            if (m_ins== nullptr)
            {
                m_ins = new T(std::forward<Args>(args)...);
            }
        }
        return m_ins;
    }
    
	// 重载函数,无需参数
    static T* instance()
    {
        return m_ins;
    }

private:
    static T* m_ins;
};
template <typename T>
T* Singleton<T>::m_ins= nullptr;

由于增加了重载instance();函数,所以之前的instance();模板函数无法再返回一个局部静态变量的形式创建类对象了,而是换成了懒汉模式单例;在重载的instance();函数中直接将创建好的指针返回即可;

使用示例:

class Test : public Singleton<Test>
{

public:
    void testFunc()
    {
        qDebug() << this << m_a << "test func" << QThread::currentThreadId();
    }

protected:
    Test(int a) : m_a(a)
    {
        qDebug() << "construct test object:" << this << a << QThread::currentThreadId();
    }

    int m_a = 0;
    friend class Singleton<Test>;
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    qDebug() << "main thread:" << QThread::currentThreadId();

    Singleton<Test>::instance(10)->testFunc();		//首次,传参数构造对象

    std::vector<std::thread> vec(10);
    for (auto& th : vec)
    {
        th = std::thread([]()
        {
            Singleton<Test>::instance()->testFunc();	//无需再传递构造参数
        });
    }

    for (auto& th : vec)
    {
        if (th.joinable())
            th.join();
    }
    return a.exec();
}

执行结果:

main thread: 0x25a0
construct test object: 0x252a924ec90 10 0x25a0
0x252a924ec90 10 test func 0x25a0
0x252a924ec90 10 test func 0x33cc
0x252a924ec90 10 test func 0x3fac
0x252a924ec90 10 test func 0x3868
0x252a924ec90 10 test func 0x230c
0x252a924ec90 10 test func 0x2528
0x252a924ec90 10 test func 0x1dac
0x252a924ec90 10 test func 0x3514
0x252a924ec90 10 test func 0x3224
0x252a924ec90 10 test func 0x3f50
0x252a924ec90 10 test func 0x18e0

可以看到,真正的类对象是在主线程中,并且构造参数为 10, 而在每个子线程中都可以访问到的都是同一个对象,并且也无需传递instance()函数参数


如果使用的是Scott Meyers单例模式(局部静态变量),那么进程结束时,会主动帮我们调用单例对象的析构函数; 而使用懒汉模式单例则不会, 且还有其他问题:
1.多线程环境下,双检查锁与CPU重新排序
2.没有手动调用单例对象的析构函数,释放其管理的资源
可以参考下一篇解决方案:跳转

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
单例模式是一种创建对象的设计模式,它保证一个类只有一个实例,并提供一个全局访问点。 下面是一个简单的C++单例模式模板示例: ```cpp #include <iostream> template<typename T> class Singleton { public: // 获取单例实例 static T& getInstance() { static T instance; return instance; } // 防止拷贝构造和赋值操作 Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; private: // 构造函数私有化 Singleton() { std::cout << "Singleton instance created." << std::endl; } ~Singleton() { std::cout << "Singleton instance destroyed." << std::endl; } }; class MyClass { public: void print() { std::cout << "Hello, World!" << std::endl; } }; int main() { // 获取MyClass的单例实例 MyClass& instance = Singleton<MyClass>::getInstance(); // 使用单例实例 instance.print(); return 0; } ``` 在上述示例中,`Singleton`是一个通用的单例模板类,通过调用`getInstance`方法可以获取该类的单例实例。构造函数和析构函数私有化,防止外部直接创建和销毁对象。此外,为了防止拷贝构造和赋值操作,使用了`delete`关键字禁用了这两个函数。 `MyClass`是一个示例类,通过单例模式创建其单例对象,并调用对象的`print`方法。 运行该程序,可以看到输出结果为“Singleton instance created.”和“Hello, World!”。使用单例模式创建的对象只有一个实例,即使多次调用`getInstance`方法,返回的也是同一个实例。 总之,单例模式模板可以通过模板类和静态方法实现一个类的单例实例,避免了多次创建和销毁对象,同时提供了全局访问点,方便对象的使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值