特殊类的设计

本文探讨了C++中的CopyBan、StackOnly、HeapOnly类,以及单例模式的饿汉、懒汉模式和禁继承特性。重点在于控制对象的创建和生命周期管理。

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

这个设计模式细节挺多,每个模式感觉好像能大致回想起来,但是总有遗漏,所以打算写一篇文章来复习一遍。

一 CopyBan

设计一个防拷贝的类

class CopyBan  不能被拷贝
{
public:
	CopyBan()可以构造
	{
		cout<<"CopyBan()"<<endl;
	}
	~CopyBan() 可以析构
	{
		cout << "~CopyBan()" << endl;
	}

	CopyBan(const CopyBan& fc) = delete; 
	CopyBan& operator=(const CopyBan& fc) = delete;

赋值也可以封,不能被拷贝可能不想自己的数据复制给其它对象。
};

还有种防拷贝的方式是如下的,

class CopyBan
{

private:
	CopyBan(const CopyBan& fc);//防拷贝2,只声明,不实现

};

虽然外部调用拷贝构造时会出错,但要编译才报错,如果直接用delete就可以在写代码阶段就报红了
设为私有是为了防止外部调用,但防不住别人去实现,实现也没用
CopyBan::CopyBan(const CopyBan& fc)
{
	cout<<"CopyBan::CopyBan(const CopyBan& fc)";
}

二 StackOnly

设计一个只能在栈上创建的类。

class StackOnly  
{
public:
    析构不能封
    ~StackOnly()
	{
		cout << "~StackOnly()" << endl;
	}
    析构函数一封,在栈上都无法创建对象,会直接报红
    static StackOnly()
   {
        StackOnly s1;

       return s1;
   }
       目前场景中感觉封不封构造都可以,因为静态对象封不住,除非把拷贝也封了
      这样静态对象就不能调用构造和拷贝构造来创建对象了。

    StackOnly(const StackOnly& s)拷贝构造可用于栈上创建对象
	{
		cout << "StackOnly(const StackOnly& s)" << endl;
	}
private:
	StackOnly()
	{
		cout << "StackOnly()" << endl;
	}
	

	void* operator new(size_t size) = delete;

	new = operator new + 构造, new可以调用拷贝和普通构造, 所以只能封operator new
		
    
};

   所以说new在最上层,其实是个关键字,可以申请空间加调用构造函数初始化,而内部封装了operator new这个函数,用于申请空间,调用构造函数有其它函数完成,此时我们在类内重载一个operator new,调用时先用类内的,而类内的已被删除,编译器选择直接报错,而不是去类外找全局的operator new,显示调用全局的用::operator new。如下使用new可能会去调用全局的operator new,所以感觉这个类啥也防不住。

void test2()
{
	StackOnly* s1 = ::new StackOnly;
	delete s1;
}

三 HeapOnly

只能在堆上创建 

class HeapOnly
{
public:
	void Delete()
	{
		delete this;
		cout << "Delete";
	}
	HeapOnly()
	{
		cout << "HeapOnly()" << endl;
	}
	HeapOnly(const HeapOnly& s)
	{
		cout << "HeapOnly(const HeapOnly& s)" << endl;
	}
	//封析构
private:
	~HeapOnly()
	{
		cout<<"~HeapOnly()"<<endl;
	}
};

  方法2,在栈上创建对象无非就是用构造和拷贝构造函数,如果我们把这两个封了,就不能在栈上创建对象了。封了构造函数,那new也就没办法调用构造函数了,所以必须提供一个公有函数,在该函数内用new, 这样new可以调用构造函数,我们也可以调用这个公有函数来创建对象。好,问题来了,如何调用这个公有函数,用对象调,对象哪里来,调用Creat这个公有函数创建对象呗,这不就逻辑死循环了吗?所以这个公有函数就必须是静态的。析构就可以不用private修饰了。

class HeapOnly2
{
public:
	static HeapOnly2* Creat()
	{
		return new HeapOnly2;
	}
	~HeapOnly2()
	{
		cout << "~HeapOnly2()" << endl;
	}
private:
	HeapOnly2()
	{
		cout << "HeapOnly2()" << endl;
	}
	HeapOnly2(const HeapOnly2& s)
	{
		cout << "HeapOnly2(const HeapOnly2& s)" << endl;
	}
};

四 单例模式之饿汉模式

   单例模式是指该类只实例化一个对象,那构造得设为private,免得让外部创建对象,那我们如何创建那个唯一的对象呢,如果创建一个全局对象,然后写一个静态函数Creat函数,返回这个对象的指针或者引用就好了,可是构造函数私有了,外部无法调用。所以还得在类内创建对象,大佬是将其设为静态成员变量。这样编译前sgl成员就会被创建。至于Creat函数是返回指针还是引用都可以。

class SingleHungry
{
public:
	static SingleHungry* Creat()
	{
		return &sgl;
	}
 
private:

    析构可以设为私有,~SingleHungry()会由于静态成员变量是类内的,可以调用private修饰的     
    ~SingleHungry()
	{
		cout<<"~SingleHungry()"<<endl;
	}
    
	SingleHungry()
	{
		cout << "SingleHungry()"<<endl;
	}

   之所以要把拷贝封了,就是防止Creat函数返回对象后被拿去拷贝对象,至于赋值应该可封可不封。

	SingleHungry(const SingleHungry& sh) = delete;

	SingleHungry operator=(const SingleHungry& sh) = delete;

	static SingleHungry sgl;
};
SingleHungry  SingleHungry::sgl;  

  sg1属于类内的成员, 所以定义时可以调用private修饰的构造函数,例如当一个静态成员函数的定义和声明分离,静态成员函数的定义内是可以调用private修饰的成员函数的,所以我认为sg1可以调用构造函数的原因和这个是类似的,祖师爷也没办法了,只能做这种特殊处理了。
 

五 单例模式之懒汉模式

        饿汉模式是定义一个静态变量,之后要使用这个类,就先调用一个Creat返回这个静态对象的指针或者引用,但是如果程序存在大量的静态变量,那在启动的时候就需要做非常多的初始化工作,一个程序启动可能要半个小时,而且最关键的是无法决定静态变量的初始化顺序,有时候B类依赖A类初始化,但是有可能B类静态对象初始化时,A类还未创建,这就出问题。

        所以我们一开始就先不创建对象,而是定义一个静态对象的指针,而且把指针初始化为空,这个时候初始化指针还不简单吗。那什么时候创建对象呢?调用Creat函数的时候就创建对象给sgl指针。

这样也就可以确定静态对象的创建顺序了,可是还有个问题就是sgl析构的问题。这个指针指向的资源如何释放,有人说,没事调用析构函数,大坑 !  sgl是new返回的,要用delete销毁,因为delete有两个作用,一个是释放sgl指向的空间,还有就是调用析构函数释放SingleLazy的对象内部的资源,举个例子,例如 string* p new string ;  delete不仅要释放p指向的这个string对象,还要调用析构函数释放内部指针指向的资源的,所以不能sgl不能直接调用析构函数。delete可以,但是有时候例如我们除了释放内存的资源外,可能还想将保存到磁盘上,那就不能单单写个delete,所以我们再写一个函数Destroy()去封装delete,此时把析构封了,免得保存操作没做就释放了,并且在delete前把资源保存到磁盘。

class SingleLazy
{
public:
	static SingleLazy* Creat()
	{
        if(sgl == nullptr)
		    sgl = new SingleLazy();
		return sgl;
	}
	static void Destroy()
	{
		string s("he");
		FILE* f = fopen("test.txt","w");
		fwrite(s.c_str(),sizeof(string),1,f);
		delete sgl;
	}
private:
	SingleLazy()
	{
		cout << "SingleLazy()" << endl;
	}
	~SingleLazy()
	{
		cout << "~SingleLazy" << endl;
	}

	SingleLazy(const SingleLazy& sh) = delete;
	SingleLazy operator=(const SingleLazy& sh) = delete;
	static SingleLazy* sgl;
};
SingleLazy* SingleLazy::sgl = nullptr;

void test9()
{
	
    SingleLazy*p=SingleLazy::Creat();
          	
	SingleLazy::Destroy(); 但是还有个缺点,就是释放还是要手动释放,
                       特别是当有十几二十个单例对象的时候,一个个Destroy会麻烦
                           能不能智能一点呢
                            
}

    噢,智能指针,用智能指针,但是智能指针一般是程序结束后自动调用析构的,我们显示调用也不是不可以,但是智能指针的析构函数就得把指针置空,不然程序结束还会再调用,也就是说程序结束都会调用类对象的析构函数,很容易二次析构,我感觉用智能指针应该是可以的。还有第二种办法就是再写一个静态对象(不能是普通,不然会逻辑死循环),当程序结束,这个静态成员也就要调用析构函数释放了,我们在函数内把单例对象的释放方法放入,也就实现了智能化的释放资源,而且还可以选择自主释放。

class Garbage Garbage是SingleLazy的内部类,方便访问sgl变量
	{
	public:
		~Garbage()
		{
            if(SingleLazy::sgl)
			    delete SingleLazy::sgl;
		}
	};
private:

	static  Garbage gb;  gb是SingleLazy成员变量,也可以写到全局,这个时候是不是静态就无所谓了

说完懒汉模式后,我再提一种自己的,根据饿汉模式优化的。而且还不用操心释放的问题。(后来看了effective c++发现早有前辈也提出这种优雅的模式)

class SingleHungry
{
public:
	static SingleHungry& Creat()
	{
		static SingleHungry sgl;
		return sgl;
	}
	
private:
	~SingleHungry()
	{
		cout << "~SingleHungry()" << endl;
	}
	SingleHungry()
	{
		cout << "SingleHungry()" << endl;
	}
	SingleHungry(const SingleHungry& sh) = delete;
	SingleHungry operator=(const SingleHungry& sh) = delete;
};

六 NonInherit

设计一个类不能被继承,最后的方法就是在该类名后加个final,这是c++11支持的。

   而c++98则是将该类构造函数设为私有,这样派生类就调不到基类的构造函数,而且你会发现这个时候派生类的默认构造函数被delete了,感觉可能是编译器发现自己的默认构造初始化不了自己的成员,就气急败坏地删了,还有更多情况会被delete,有兴趣可了解了解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小何只露尖尖角

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

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

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

打赏作者

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

抵扣说明:

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

余额充值