C++单例模式与特殊类的设计

一、设计一个不能被拷贝的类

拷贝只会发生在拷贝构造和赋值运算符重载中,所以如果想要设计一个不能被拷贝的类,只需要让这个类的对象在外部不能调用拷贝构造和赋值运算符重载即可。

C++98设计不能被拷贝的类是通过将拷贝构造函数和赋值运算符重载函数设置成私有的,并且只声明不定义。设置成私有的是防止用户在外部自己定义函数,只声明不定义的目的是不让编译器生成默认的拷贝构造函数和赋值运算符重载函数。

// 设计一个不能被拷贝的日期类
class Date
{
public:
    Date(int year = 1, int month = 1, int day = 1)
        : _year(year), _month(month), _day(day)
    {
    }

private:
    Date(const Date &date);
    Date &operator=(const Date &date);

private:
    int _year;
    int _month;
    int _day;
};

C++11扩展了delete的用法,delete除了释放new申请的资源以外,如果在默认成员函数后面跟上=delete,表示让编译器删除该默认成员函数。

// 设计一个不能被拷贝的日期类
class Date
{
public:
    Date(int year = 1, int month = 1, int day = 1)
        : _year(year), _month(month), _day(day)
    {
    }


    Date(const Date &date) = delete;
    Date &operator=(const Date &date) = delete;

private:
    int _year;
    int _month;
    int _day;
};

二、设计一个只能在堆上创建对象的类

我们创建对象时分别有三种场景:在栈上创建对象、在静态区创建对象、在堆上创建对象。

int main()
{
    Date d1;// 在栈上创建对象
    static Date d2;// 在静态区创建对象
    Date* d3 = new Date;// 在堆上创建对象 
    return 0;
}

所以如果要设计一个只能在堆上创建对象的类,就必须让类在构造对象时去堆上动态申请空间,我们可以把构造函数设置成为私有,不让外部调用构造函数,然后再提供一个创建对象的函数,这个函数返回堆上申请的对象。

下面代码中getObj函数必须设置成静态的,因为如果不设置成静态的,它需要有对象才能调得动,而我们本身就是要利用它来创建对象,这就矛盾了。设置成静态的以后,由于构造函数的调用不需要this指针,所以即使静态的getObj函数没有this指针也可以调用构造函数

#include <iostream>

using namespace std;

// 设计一个只能在堆上创建对象的类
class Date
{
public:
    static Date* getObj(int year = 1, int month = 1, int day = 1)
    {
        return new Date(year, month, day);
    }

private:
    Date(int year = 1, int month = 1, int day = 1)
        : _year(year), _month(month), _day(day)
    {
    }

private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date* d1 = Date::getObj();
    return 0;
}

但这样的代码还存在一些小缺陷,我们的要求是一定只能在堆上创建对象,但如果我们在堆上创建一个对象,再利用拷贝构造函数创建一个新的对象,这个新创建的对象依旧是在栈上的,也可以是在静态区的,这就不满足我们的要求了。

int main()
{
    Date* d1 = Date::getObj();
    Date d2(*d1);
    static Date d3(*d1);
    return 0;
}

所以我们必须将其拷贝构造函数设置成为删除函数,这样才能避免这种情况发生:

// 设计一个只能在堆上创建对象的类
class Date
{
public:
    static Date* getObj(int year = 1, int month = 1, int day = 1)
    {
        return new Date(year, month, day);
    }

    Date(const Date& date) = delete;

private:
    Date(int year = 1, int month = 1, int day = 1)
        : _year(year), _month(month), _day(day)
    {
    }

private:
    int _year;
    int _month;
    int _day;
};

但是赋值运算符重载函数并不需要像拷贝构造函数一样设置,因为赋值运算符重载函数是两个已经存在的对象之间赋值,不会出现创建对象的情况。

三、设计一个只能在栈上创建对象的类

这个与上面的只能在堆上创建对象的类相似,将构造函数私有化,然后设计静态函数创建一个栈上的对象以后返回。

// 设计一个只能在堆上创建对象的类
class Date
{
public:
    static Date getObj(int year = 1, int month = 1, int day = 1)
    {
        return Date(year, month, day);
    }

    void *operator new(size_t size) = delete;
    void operator delete(void *p) = delete;

private:
    Date(int year = 1, int month = 1, int day = 1)
        : _year(year), _month(month), _day(day)
    {
    }

private:
    int _year;
    int _month;
    int _day;
};

四、设计一个不能被继承的类

C++98中将一个类设计成不能被继承的方法是将父类的构造函数设置成为私有的,只提供静态函数来给外部创建父类对象,子类想要继承会报错,因为父类的私有成员对子类是不可见的,所以子类无法调用构造函数也就无法创建对象,从而无法继承父类。

C++11新增了final关键字,凡是用final修饰的类都不能被继承,非常简单粗暴:

class A  final
{
    // ....
};

五、单例模式

1.单例模式的概念

单例模式是一种软件开发时经常使用到的设计模式,设计模式是一套被反复使用、多数人知晓的、经过分类的代码设计经验总结。使用设计模式是为了提高代码的可重用性,让代码更容易被他人理解,保证代码的可靠性。设计模式使代码编写真正工程化。

单例模式指的是一个类只能创建一个对象,该模式可以保证系统中该类只有一个实例,并且提供了一个访问它的全局访问点,该实例被所有程序模块所共享

比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其它对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

单例模式的实现有两种方式,分别是饿汉模式懒汉模式

2.饿汉模式

饿汉模式指的是不管将来会不会被用到,只要在程序启动的时候就会在全局创建一个唯一的实例对象,当需要使用的时候直接拿来用就可以了,不需要等到要使用的时候再创建。

这就很形象地比喻成为一个饿汉,他肚子非常饿,他找到一家餐厅吃饭,他希望餐厅里早早就准备好了可以吃的东西,等他来到了直接就可以吃了,不需要再等待。

我们模拟一个场景演示一下饿汉模式下的单例:假设我们有一个个人信息类,里面记录用户的个人信息。然后我们有多个线程,这些线程可能在某些时候需要使用到用户的个人信息,但它们访问到的个人信息必须是同一份,所以需要用单例模式限制只创建一个对象给所有线程使用。

#include <iostream>
#include <string>

using namespace std;

class Info
{
public:
    // 外部获取单例对象的接口
    static Info* getInstance()
    {
        return m_instance;
    }

    // 拷贝构造函数必须设置成删除函数
    Info(const Info& info) = delete;

    void setName(const string& name)
    {
        _name = name;
    }

    string& getName()
    {
        return _name;
    }

    void setAddress(const string& address)
    {
        _address = address;
    }

    string& getAddress()
    {
        return _address;
    }

private:
    Info()
    {}

    string _name;
    string _address;
    static Info* m_instance;// 单例对象的声明
};

// 单例对象的定义
Info* Info::m_instance = new Info;

int main()
{
    // info1和info2其实是两个指针指向同一个对象
    Info* info1 = Info::getInstance();
    Info* info2 = Info::getInstance();

    info1->setName("张三");
    cout << info2->getName() << endl;

    info2->setAddress("广东省广州市天河区");
    cout << info1->getAddress() << endl;
    return 0;
}

3.懒汉模式

懒汉模式顾名思义就是比较懒,如果你不是需要使用我是不会提前创建好对象的,等你要使用的时候再说吧。也就是说懒汉模式的思路是当没有人使用这个类的时候,就不创建对象。等到有人需要使用的时候再来创建对象。

还是以上面个人信息类为例子演示一下懒汉模式下的单例:

#include <iostream>
#include <string>
#include <mutex>

using namespace std;

class Info
{
public:
    // 外部获取单例对象的接口
    static Info *getInstance()
    {
        if (m_instance == nullptr)
        {
            m_mutex.lock();
            // 当第一次有人调用这个接口时
            // 说明他需要使用对象,此时再为他创建对象
            if (m_instance == nullptr)
            {
                m_instance = new Info;
            }
            m_mutex.unlock();
        }

        return m_instance;
    }

    // 拷贝构造函数必须设置成删除函数
    Info(const Info &info) = delete;

    void setName(const string &name)
    {
        _name = name;
    }

    string &getName()
    {
        return _name;
    }

    void setAddress(const string &address)
    {
        _address = address;
    }

    string &getAddress()
    {
        return _address;
    }

private:
    Info()
    {
    }

    string _name;
    string _address;
    static Info *m_instance; // 单例对象的声明
    static mutex m_mutex;
};

// 单例对象的定义,懒汉模式不会先创建对象
Info *Info::m_instance = nullptr;

int main()
{
    // info1和info2其实是两个指针指向同一个对象
    Info *info1 = Info::getInstance();
    Info *info2 = Info::getInstance();

    info1->setName("张三");
    cout << info2->getName() << endl;

    info2->setAddress("广东省广州市天河区");
    cout << info1->getAddress() << endl;
    return 0;
}

4.饿汉模式和懒汉模式对比

  1. 饿汉模式由于是在程序运行起来以后就创建对象,如果我们定义了另一个全局变量,并且这个全局变量与饿汉模式创建的对象有依赖关系,那就有可能会出现报错,因为我们无法确定谁先创建。
  2. 饿汉模式不可避免的问题是程序初始化会比较慢,因为程序一启动就要创建对象。
  3. 懒汉模式可以控制对象的创建顺序,并且只会在第一次要使用对象的时候才创建对象,这可以延迟加载初始化,不影响程序启动。
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值