【C++】特殊类设计

目录

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

2、设计一个只能在堆上创建空间的类

2.1 方法一

2.2 方法二

3、设计一个只能在栈上创建空间的类

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

5、设计一个只能创建一个对象的类(单例模式)

5.1 设计模式

5.2 饿汉模式

5.3 懒汉模式


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

拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。

在C++98中,将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可

class CopyBan
{
	// ...
private:
	CopyBan(const CopyBan&);
	CopyBan& operator=(const CopyBan&);
	//...
};

原因:
1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就不能禁止拷贝了

2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。

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

class CopyBan
{
// ...
CopyBan(const CopyBan&)=delete;
CopyBan& operator=(const CopyBan&)=delete;
//...
}

2、设计一个只能在堆上创建空间的类

2.1 方法一

正常的类是可以在栈上、堆上、静态区开空间的

class HeapOnly
{

};
int main()
{
	HeapOnly hp1;
	HeapOnly* hp2 = new HeapOnly;
	static HeapOnly hp3;
	return 0;
}

这三种创建对象的方式都需要用到构造函数,所以我们可以将构造函数写为私有,然后提供一个统一创建对象的函数CreateObj,CreateObj里面实现为在堆上创建空间。注意,因为构造函数被写成了私有,所以没办法先创建一个类对象,然后利用这个对象去调用CreateObj,所以CreateObj需要写成static

class HeapOnly
{
public:
	static HeapOnly* CreateObj()
	{
		return new HeapOnly;
	}
private:
	HeapOnly()
	{}
};
int main()
{
	HeapOnly::CreateObj();
	return 0;
}

此时是有缺陷的,因为有拷贝构造和赋值运算符重载可以在栈上开空间

class HeapOnly
{
public:
	static HeapOnly* CreateObj()
	{
		return new HeapOnly;
	}
private:
	HeapOnly()
	{}
};
int main()
{
	HeapOnly* hp1 = HeapOnly::CreateObj();
	HeapOnly hp2(*hp1);
	HeapOnly hp3 = *hp1;
	return 0;
}

所以我们需要将拷贝构造和赋值运算符重载也封了

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

注意:这里不能将构造函数封了,只能设置为私有,因为CreateObj里面的new需要调用构造函数
要释放时直接delete

int main()
{
	HeapOnly* hp1 = HeapOnly::CreateObj();
	delete hp1;
	return 0;
}

2.2 方法二

方法一是将构造函数私有化,还可以将析构函数私有化,并提供Destory函数

class HeapOnly
{
public:
	void Destory()
	{
		delete this;
	}
private:
	~HeapOnly()
	{}
};
int main()
{
	HeapOnly hp1;
	HeapOnly* hp2 = new HeapOnly;
	static HeapOnly hp3;
	return 0;
}

此时只有hp2是可以创建成功的,hp1和hp3创建失败是因为无法调用析构函数,hp2之所以能够成功,是因为new的对象是只管开辟空间,释放需要自己调用delete,此时delete hp2是会报错的,因为delete会去调用析构函数,而析构函数是私有的,这也是为什么要写一个Destory函数的原因

此时就没有必要将拷贝构造等封死了,因为没有析构函数,无法创建对象

3、设计一个只能在栈上创建空间的类

将构造函数封死,提供一个CreateObj,这个CreateObj内部实现为在栈上开空间

class StackOnly
{
public:
	static StackOnly CreateObj()
	{
		return StackOnly();
	}
private:
	StackOnly()
		:_a(0)
	{}
private:
	int _a;
};
int main()
{
	StackOnly s1 = StackOnly::CreateObj();
	return 0;
}

这样是有问题的,依然可以利用拷贝构造在堆上开空间

int main()
{
	StackOnly s1 = StackOnly::CreateObj();
	StackOnly* s2 = new StackOnly(s1);
	return 0;
}

这里不能将拷贝构造封死,因为CreateObj是传值返回的(局部对象只能走传值返回),会调用拷贝构造,所以不能封死CreateObj。new是由operator new和构造组成的,当然,在s2这里是operator和拷贝构造组成的,operator是一个全局函数,C++规定,当一个类重载了operator new 后,则只会调用自己的,所以此时可以重载一个专属的operator new,并将这个函数delete,可以顺便将operator delete也delete

class StackOnly
{
public:
	static StackOnly CreateObj()
	{
		return StackOnly();
	}
	void* operator new(size_t size) = delete;
	void operator delete(void* p) = delete;
private:
	StackOnly()
		:_a(0)
	{}
private:
	int _a;
};

此时依然有问题,可以在静态区开空间,因为依然有拷贝构造

int main()
{
	StackOnly s1 = StackOnly::CreateObj();
	static StackOnly s3(s1);
	return 0;
}

此时可以直接封死拷贝构造,写一个移动构造

class StackOnly
{
public:
	static StackOnly CreateObj()
	{
		return StackOnly();
	}
	StackOnly(StackOnly&& s)
	{}
	StackOnly(const StackOnly& s) = delete;
private:
	StackOnly()
		:_a(0)
	{}
private:
	int _a;
};

此时CreateObj中的值就是右值,刚好调用移动构造,即使CreateObj中的是左值,也可以move,这样在返回过程中就只会调用移动构造了

此时还是有问题,像下面的情况都行

int main()
{
	StackOnly s1 = StackOnly::CreateObj();
	StackOnly* s2 = new StackOnly(move(s1));
	static StackOnly s3(move(s1));
	return 0;
}

所以还是上面封死operator new的方法好

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

C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承

class NonInherit
{
public:
    static NonInherit GetInstance()
    {
        return NonInherit();
    }
private:
    NonInherit()
    {}
};

C++11中,新增了final关键字,final修饰类,表示该类不能被继承

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

5、设计一个只能创建一个对象的类(单例模式)

5.1 设计模式

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。

使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

我们前面学习过的迭代器模式、适配器模式也都是设计模式

单例模式

一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
注意:这个对象创建在哪里都行,只要只有一个对象就行
实现单例模式主要有两种方式:饿汉模式和懒汉模式

5.2 饿汉模式

就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象
在类中创建一个静态的类对象,这个类对象不属于类,是一个全局对象,放在类中只是为了让其受到类域的限制,这个类的成员变量只有前面那些。放在类里面只是声明,还需要在类外面定义以下。注意,还需要将构造函数封死。这样在类外部就无法创建类对象,整个程序就只有类里面的那个全局对象,这个全局对象是还没有进入main时类对象就已经创建了

class InfoMgr
{
public:
	static InfoMgr& GetInstance()
	{
		return _ins;
	}
	void Print()
	{
		cout << _ip << " " << _port << " " << _buffSize << endl;
	}
private:
	InfoMgr()
	{}
private:
	string _ip = "127.0.0.1";
	int _port = 80;
	size_t _buffSize = 1024 * 1024;
	// ...
	static InfoMgr _ins;
};
InfoMgr InfoMgr::_ins;
int main()
{
	InfoMgr::GetInstance().Print();
	InfoMgr im = InfoMgr::GetInstance();
	im.Print();
	return 0;
}

此时仍然可以使用拷贝构造来再创建一个对象,所以封死拷贝构造

int main()
{
	InfoMgr copy(InfoMgr::GetInstance());
	return 0;
}
class InfoMgr
{
public:
	static InfoMgr& GetInstance()
	{
		return _ins;
	}
	void Print()
	{
		cout << _ip << " " << _port << " " << _buffSize << endl;
	}
	InfoMgr(const InfoMgr& io) = delete;
	InfoMgr& operator=(const InfoMgr& io) = delete;
private:
	InfoMgr()
	{}
private:
	string _ip = "127.0.0.1";
	int _port = 80;
	size_t _buffSize = 1024 * 1024;
	// ...
	static InfoMgr _ins;
};
InfoMgr InfoMgr::_ins;

饿汉模式有两个缺陷。第一,饿汉模式中这个唯一的类对象是在运行main函数之前就创建好的,也就是在main函数之前就会调用构造函数,若调用构造函数的消耗较大,如需要读取大文件,且有多个单例对象时,程序启动会非常慢。第二,若有A和B两个饿汉,且对象初始化存在依赖关系,要求A先初始化,B后初始化,饿汉无法完成。

5.3 懒汉模式

懒汉模式就是在要用时再创建对象(第一次调用时创建对象)
也需要将构造函数设为私有,封死拷贝构造和赋值运算符重载。
此时是用一个类对象的指针。饿汉模式不存在释放问题,因为是全局对象,懒汉是new的,需要手动释放

class InfoMgr
{
public:
	static InfoMgr& GetInstance()
	{
		// 第一次调用时创建单例对象
		if (_pins == nullptr)
			_pins = new InfoMgr;
		return *_pins;
	}
	static void DelInstance()
	{
		delete _pins;
		_pins = nullptr;
	}
	void Print()
	{
		cout << _ip << " " << _port << " " << _buffSize << endl;
	}
	InfoMgr(const InfoMgr& io) = delete;
	InfoMgr& operator=(const InfoMgr& io) = delete;
private:
	InfoMgr()
	{
		cout << "InfoMgr()" << endl;
	}
private:
	string _ip = "127.0.0.1";
	int _port = 80;
	size_t _buffSize = 1024 * 1024;
	// ...
	static InfoMgr* _pins;
};
InfoMgr* InfoMgr::_pins = nullptr;
int main()
{
	InfoMgr::GetInstance().Print();
	InfoMgr::GetInstance().Print();
	return 0;
}

结果是

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值