特殊类创建

总结:特殊类创建流程 = 私有构造函数+静态成员

创建一个只能在堆上创建对象的类

方法:先把构造函数私有,这样就不能让用户随意创建对象了。然后我们再用静态成员来进行操作。


class HeapOnly
{
public:
	static HeapOnly* createObj()
	{
		return new HeapOnly();
	}
private:
	HeapOnly() {}
	HeapOnly(const HeapOnly&) = delete;
};

int main()
{
	HeapOnly* foo = HeapOnly::createObj();
	HeapOnly cp(*foo);
}

讲几个点:
1.HeapOnly(const HeapOnly&) = delete;是C++11的语法,表示这个函数不能被使用。且写函数声明即可,不用写实现。

2.这里为什么要禁用掉拷贝构造呢?可能有这种写法:
HeapOnly cp(*foo);这样就被钻漏洞了。

3.赋值运算符重载不需要禁用。没办法用这个来钻漏洞。

创建一个只能在栈上创建对象的类

还是先私有构造,然后用静态成员返回对象。

class StackOnly
{
public:
	static StackOnly createObj()
	{
		return StackOnly();
	}
private:
	StackOnly() {};
};

int main()
{
	StackOnly foo(StackOnly::createObj());
}

讲几个点:
1.这里的createObj的返回值不能传引用,因为StackOnly()是局部对象。

2.由于要拷贝构造一个返回值对象,因此不能禁用掉拷贝构造

创建一个不允许拷贝对象的类

把拷贝构造和赋值运算符重载私有即可(或者这两个函数写成delete)

class NoCopy
{
public:
	NoCopy() {};
	NoCopy(const NoCopy&) = delete;
	NoCopy& operator=(NoCopy) = delete;
};

创建一个不允许被继承的类

之前讲继承的时候讲过final关键字,就是不允许被继承。

class NoExtends final
{
public:
	NoExtends() {};
};

还有一种写法是把构造函数私有了,这样就不能被子类调用父类的构造函数来初始化子类对象了。

但是这样写其实不太好,因为要在类外面创建父类对象就必须写两个静态成员,一个是可以在栈上创建对象的createObj,一个是可以在堆上创建对象的createObj

单例模式

只能创建一个对象的类,叫单例模式。
创建步骤如下:
1.先私有构造函数,再把拷贝构造和赋值禁掉(禁不禁不影响最后结果,只影响效率,因为同一个对象进行拷贝是无用的)
2.写一个getInstance返回对象
3.单例对象的指针都是静态成员,只有静态的才能使对象只有一份。

饿汉单例

程序开始main执行之前就创建单例对象(因为单例对象是静态成员,main之前就初始化了)。要用的时候直接用。

饿汉单例不好,因为如果单例对象太大,迟迟进入不了main函数。程序员无法判定是服务出bug了还是单例加载太慢了。

代码:

class Singleton
{
public:
	static Singleton* getInstance()
	{
		return ptr;
	}
private:
	Singleton() {};
	static Singleton* ptr;
};

Singleton* Singleton:: ptr = new Singleton;

懒汉单例

懒汉单例指你要使用这个单例模式的时候再创建单例对象。

优点:可以让程序跑起来再创建单例,不会让服务卡死。

代码:

class Singleton
{
public:
	static Singleton* getinstance()
	{
		if (ptr == nullptr)
		{
			mtx.lock();
			if (ptr == nullptr)
			{
				ptr = new Singleton();
			}
			mtx.unlock();
		}
		return ptr;
	}
private:
	Singleton() {};
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;

	static Singleton* ptr;
	static mutex mtx;
};

Singleton* Singleton::ptr = nullptr;
mutex Singleton::mtx;

上面是单例的核心代码。私有构造,禁用拷贝和赋值都是常规操作。懒汉的最大特点就是double-check + mutex

1.为什么需要加锁呢?

因为这有线程安全问题。

在这里插入图片描述

其实由于对象只有一个,这个对象你可以看成临界资源。两个线程对临界资源进行写操作肯定要加锁。

2.至于为什么要double-check?
原因是要提升效率。我们可以看到,如果不double-check。在第一次创建好单例对象之后,第二次再次进入,由于此时ptr一定不为空,此次判断是没有意义的。但是却要为这次无用的判断加锁。

我们知道加锁,要从用户态中断陷入(trap指令)到内核态,成本很高,会影响效率,因此我们要进行double-check。

如果先判断了ptr不为空, 线程就不要加锁了,直接返回单例对象的地址即可。如果判断了ptr为空, 证明有可能单例对象没有创建好,就去申请锁再次判断一下。

double-check的思想可以广泛用于只需要加锁一次的情景!(如果这个资源访问时要频繁加锁,就不能用)

3.加锁在linux下是用POSIX库的,但是由于windows下不支持这个库,你要使用就要下载这个库。因此C++为了支持跨平台,C++11用oop思想封装了一个线程库。

封装原理用到了条件编译:

#ifdef _WIN32
// windows 提供多线程api 
#else
// linux pthread
#endif // _WIN32

4.可以为懒汉模式加上一个GC(garbage collection)。但这不是必须的,由于单例模式的对象不析构也没什么关系,一般经常使用的对象才会用单例模式,像httpServer这样的对象。

写一个内部类GC,当GC的生命周期结束的时候,它会析构掉单例对象。

class GC
{
public:
	~GC()
	{
		if (ptr)
		{
			delete ptr;
			ptr = nullptr;
		}
	}
};

总体代码:

class Singleton
{
public:
	static Singleton* getinstance()
	{
		if (ptr == nullptr)
		{
			mtx.lock();
			if (ptr == nullptr)
			{
				ptr = new Singleton();
			}
			mtx.unlock();
		}
		return ptr;
	}
private:
	Singleton() {};
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;

	class GC
	{
	public:
		~GC()
		{
			if (ptr)
			{
				delete ptr;
				ptr = nullptr;
			}
		}
	};

	static Singleton* ptr;
	static mutex mtx;
	static GC gc;
};

Singleton* Singleton::ptr = nullptr;
mutex Singleton::mtx;
Singleton::GC Singleton::gc;

讲一下:
其实为了对象唯一,因此我们可以看到基本所有成员变量都是static的。
所以以后我们写代码想写只有唯一对象的时候,也可以用static成员变量。(其实就和写算法题的全局变量没什么区别)。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值