C++只能在栈上创建对象、只能在堆上创建对象和单例模型

本文探讨了C++中如何限制对象在堆或栈上的创建,深入解析单例模式的两种实现:饿汉模式与懒汉模式。通过代码示例,详细讲解了线程安全和资源管理策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

面试题1:只能在堆上创建对象 

class HeapOnly
{
public:
	//静态成员(静态区)都是在程序结束释放
    //必须是静态的,否则该函数需要对象调用
	static HeapOnly* Create(int data = 0)
	{
		HeapOnly *p = new HeapOnly;
		p->_data = data;
		return p;
		//不可以直接 return new HeapOnly(data);因为调用构造函数之后data值为随机值。
	}
	//C++11新的屏蔽一个函数
	//1、如果是delete表示这个函数被取消
	//2、如果是default表示这个函数按照编译器默认生成
	//3、可以给为私有的或者共有的,都可以实现相应的功能
	//HeapOnly(const HeapOnly& h) = default;
	HeapOnly(const HeapOnly& h) = delete;

private:
	HeapOnly(int data = 0){}
	//C++98屏蔽一个函数:
	//1、只声明,不给定义,说不定实现特别麻烦
	//2、给为私有的
	//HeapOnly(const HeapOnly& h);
	int _data;
};

面试题2:只能在栈上创建对象

//缺点可以在全局创建对象,既不在栈也不再堆。
class StackOnly
{
public:
	StackOnly(int data = 0)
		:_data(data)
	{}
	//屏蔽operator new相当于屏蔽了定位new和new。
	void *operator new(size_t size) = delete;
private:
	int _data;
};

 上述代码虽然实现了不能在堆上创建对象,但是可以在全局位置创建,导致对象位置实在全局数据区。怎么改进呢,想办法不能在全局位置创建,则可以使用栈的思想,封装一个静态函数(并且吧构造函数设置为私有的),就可以实现只能在栈上创建对象。为什么呢,一个光杆子函数起初只能在函数内部调用。

class StackOnly
{
public:
	static StackOnly Create()
	{
		return StackOnly();
	}
	void *operator new(size_t size) = delete;
private:
	StackOnly(){}
	StackOnly& operator=(const StackOnly& s);
	StackOnly(const StackOnly& s);
};

单例模式

一个类只能创建一个对象就是单例模型,该模式可以保证该类在系统中只有一个实例,并提供一个访问它的全局访问点,该实例可以被所有程序模块使用。

单例模式分类:

(1)饿汉模式:不管用不用程序一起动就创建唯一一个实例化对象。

饿汉模式条件:(1)程序启动创建对象 (2)只能实例化一个对象

设计思路:首先考虑程序启动就需要创建对象则我们想到了在内存中处于数据区的元素是在编译期间就创建好了,所以我们可以考虑把该对象定义为静态或者全局(全局舍弃,不可访问类内私有成员)。所以就可以定义一个静态成员对象,该对象在程序运行就已经创建好了。在考虑第二个条件,只能实例化一个对象,那么除了编译期间自动创建的一个对象,我们在函数内不可以在创建其他对象,只能调用该对象。所以吧构造函数、拷贝构造函数和赋值运算符重载都定义为私有的,当然后两个只可以给声明或者采用C++11方式删掉两个函数。

至此,我们已经构造好了唯一一个对象,最后就是怎么来调用它,当然使用静态成员函数,并且该函数返回值必须为对象的地址或者引用,不可以返回值,因为返回值相当于就创建了临时对象,吧临时对象返回。又因为普通成员函数只能通过对象来调用,但是我们现在只有一个对象,而且反而还想要获取该对象,所以不可以使用普通成员函数返回该对象。

// 饿汉模式
// 优点:简单
// 缺点:可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定。
class Singleton
{
public:
	static Singleton* GetInstance()
	{
		return &m_instance;
	}
	friend ostream& operator<<(ostream& _cout, Singleton& s);
private:
	Singleton(int data = 0)//私有化构造函数
	{
		_data = data;
	}
	Singleton(const Singleton& s);//私有化拷贝构造函数
	Singleton operator=(const Singleton& s);//私有化赋值运算符重载
	int _data;
	static Singleton m_instance;
};
Singleton Singleton::m_instance(1);

ostream& operator<<(ostream& _cout, Singleton& s)//重载输出
{
	_cout << s._data;
	return _cout;
}

void test()
{
	cout << *(Singleton::GetInstance())<< endl;
}

优点:饿汉模式适用于多线程高并发,可以避免资源竞争,提高速率。

缺点:如果单例模式构造十分耗时或者占用资源,比如要读取文件,加载插件等,就会使得程序运行起来特别慢,但是我们在程序运行也不需要该对象,而她又创建对象,使得拖慢程序运行。所以可以采用延迟创建,这就是懒汉模式。

(2)懒汉模式:在第一次使用对象时,创建唯一一个对象。

class Singleton
{
public:
	static Singleton* GetInstance()
	{
		//此处两层判断,为了就是提高效率
		//此处使用到了锁,为了就是保证线程安全(同步和互斥)
        /*if (nullptr == m_pInstance)
		{
			m_pInstance = new Singleton;
		}*/
		if (nullptr == m_pInstance)
		{
			m_tex.lock();
			if (nullptr == m_pInstance)
			{
				m_pInstance = new Singleton;
			}
			m_tex.unlock();
		}
		return m_pInstance;
	}
	
	//定义一个内部类。程序结束回收对象,因为是new申请了空间
	class cycle
	{
	public:
		~cycle()
		{
			if (m_pInstance != nullptr)
			{
				delete m_pInstance;
				m_pInstance = nullptr;
			}
		}
	private:
        //定义一个静态回收类对象
		static cycle endflag;
	};
	
	Singleton(const Singleton& s) = delete;
	Singleton operator=(Singleton& s) = delete;
private:
	Singleton(){}
	static Singleton *m_pInstance;//单例对象指针
	static mutex m_tex;
};
Singleton* Singleton::m_pInstance = nullptr;
mutex Singleton::m_tex;
Singleton::cycle Singleton::cycle::endflag;

void fun(int n)
{
	cout << Singleton::GetInstance() << endl;
}

int main()
{
	thread t1(fun, 1);
	thread t2(fun, 2);
	thread t3(fun, 3);
	t1.join();
	t2.join();
	t3.join();
	return 0;
}

由于设计思路和饿汉模式差不多,故此处不讲思路,只解决下面几点问题:

(1)为什么使用静态单例模式,而不是静态成员变量(不是指针形式的变量)?

        为了后续判断。因为编译期间创建这个对象已经创建好了,只不过它是nullptr,所以当我们第一次使用(也就是第一次调用满足为nullptr)就给他开辟了一段空间,后序在调用就不满足nullptr条件,所以就不在开辟空间,实现了单例模式要求。但是如果使用其他不适指针变量的形式,虽然也可以实现,但是需要给他一个flag,而且实现起来比较麻烦,所以采用指针形式的变量。

(2)为什么要加锁?

        其实加锁就是为了保证线程安全,线程安全就是同步与互斥(同步:访问临界资源的时序性;互斥:同一时间只能有一个线程访问资源)。如果我们不加锁,几个线程几乎同时到达,而且new需要三步(1、开辟空间 2、调用构造函数3、赋值)才能完成变量的实例化,在这期间,就会导致多个线程同时进入if语句操作对象。所以我们需要加锁。

(3)为什么使用两次if语句判空?

(3)为什么还要一个内部类?

        其实这个内部类是专门用于释放该对象资源的,因为内部类销毁是在程序结束时,所以可以定义一个内部类静态对象,然后让这个对象的析构函数释放单例对象的资源。内部类是外部类的友元类。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_41318405

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值