C++:特殊类的设计

目录

一. 禁止被拷贝的类

二. 只能在堆区创建对象的类

三. 只能在栈区创建对象的类

四. 不能被继承的类

五. 只能实例化出一个对象的类 -- 单例模式


一. 禁止被拷贝的类

  • C++98实现方法

将拷贝构造函数和赋值运算符重载函数设置为私有,只声明不实现。这样如果用户试图在类外部拷贝构造或赋值,编译器禁止这么做,因为相关函数为私有属性,类外部没有访问权限。

注意:这里必须强制编译器生成默认构造函数或显示定义构造函数,因为声明了拷贝构造函数之后,编译其正常情况下不会再自动生成默认构造函数。

class CopyForbid
{
public:
	CopyForbid() = default;
	// ... ...

private:
	CopyForbid(const CopyForbid& noCopy);
	CopyForbid& operator=(const CopyForbid& noCopy);
};
  • C++11实现方法

C++11赋予了delete关键字新功能:修饰成员函数,禁止特定成员函数的生成。只需要声明拷贝构造函数和赋值函数,然后用delete修饰它们即可。

这里也必须强制生成或显示定义构造函数,声明了拷贝构造编译器就不会自动生成默认构造,哪怕拷贝构造被delete修饰禁止生成,但它还是有声明。

class CopyForbid
{
public:
	CopyForbid() = default;

	CopyForbid(const CopyForbid& noCopy) = delete;
	CopyForbid& operator=(const CopyForbid& noCopy) = delete;

private:
	// ...
};

二. 只能在堆区创建对象的类

  • 方法1:析构函数私有化,提供专门的接口函数清理资源

由于析构函数被私有化,直接在栈区创建对象的话,对象生命周期结束要调用析构函数,但类外部没有调用析构函数的权限,所以无法就在在栈区创建对象。

这样就只能通过new在堆区动态申请内存空间来创建类对象,当对象不再使用后要显示调用Destroy函数来清理资源,否则会造成内存泄漏问题。

采用析构函数私有化的方法不需要考虑通过拷贝在栈区创建了对象的问题,因为只要在栈区创建对象,它的生命周期结束时就必须调用析构函数,调不到析构函数就会报错。

class HeapOnly
{
public:
	HeapOnly(int x = 0)
		: _x(x)
	{ }

	void Destory()  //专门用于资源清理的接口函数
	{
		delete this;
	}
	
	// ...

private:
	~HeapOnly()
	{
		std::cout << "~HeapOnly()" << std::endl;
	}

	int _x;
};

int main()
{
	//HeapOnly hp1;  //禁止hp1的创建

	HeapOnly* ptr = new HeapOnly;  //在堆区创建对象
	ptr->Destory();  // 输出 ~HeapOnly()

	return 0;
}
  • 方法2:私有化构造函数,提供静态成员函数获取指向对象的指针

由于构造函数私有,不能直接在栈区创建对象,只能通过调用特定的静态成员函数在堆区创建对象。这里应当禁止拷贝构造函数和赋值函数的生成,以防止用创建在堆区上的对象,通过拷贝构造在栈区创建对象。

class HeapOnly
{
public:
	//对象创建接口函数
	static HeapOnly* CreatInstacne(int x = 0)
	{
		return new HeapOnly(x);
	}

	//析构函数
	~HeapOnly()
	{
		std::cout << "~HeapOnly()" << std::endl;
	}

	//禁止拷贝构造函数和赋值函数的生成
	HeapOnly(const HeapOnly& hp) = delete;
	HeapOnly& operator=(const HeapOnly& hp) = delete;

	// ...

private:
	HeapOnly(int x)
		: _x(x)
	{ }

	int _x;
};

int main()
{
	HeapOnly* ptr = HeapOnly::CreatInstacne(1);   //调用静态成员函数创建对象
	// ... 使用对象
	delete ptr;  //释放对象资源
	return 0;
}

三. 只能在栈区创建对象的类

将构造函数私有化,提供特定的静态成员函数GetInstance创建对象。这里的GetInstance函数只能只通过拷贝对象来返回,因为要在栈区创建对象,而GetInstance函数中调用私有构造函数创建的对象出了函数作用域就会被销毁,必须通过对象的拷贝来获得对象。因此,StackOnly类不能强行禁止拷贝构造函数和赋值函数的生成。

但是如何不禁止拷贝构造函数的生成,就可能会有用户使用已经在栈区创建出来的对象,通过拷贝构造,在堆区通过new创建对象。可以通过在类内部禁止operator new函数的生成来杜绝通过拷贝构造在堆区new对象的问题,因为new的底层要调用operator new函数,而如果局部声明了operator new函数,会先去调用局部,但是局部的operator函数被delete修饰禁止生成,所有new就调不到operator new函数,也就杜绝了通过拷贝构造在堆区创建对象的问题。

但是,通过定义static属性的类对象在静态区创建对象没有办法杜绝,但这种情况极少出现,可以不予考虑。

class StackOnly
{
public:
	//对象获取函数
	static StackOnly GetInstance(int x = 0)
	{
		StackOnly ret(x);
		return ret;
	}

	//不能禁止拷贝构造和赋值函数的生成
	//否则就无法接收GetInstance函数的返回值
	/*StackOnly(const StackOnly& st) = delete;
	StackOnly& operator=(const StackOnly& st) = delete;*/

	//应当禁止operator new函数的生成
	void* operator new(size_t n) = delete;

	//析构函数
	~StackOnly()
	{
		std::cout << "~StackOnly()" << std::endl;
	}

private:
	//私有化构造函数
	StackOnly(int x)
		: _x(x)
	{ }

	int _x;
};

int main()
{
	StackOnly st = StackOnly::GetInstance(1);
	//StackOnly* pst = new StackOnly(st); //无法调用operator new
	return 0;
}

四. 不能被继承的类

  • C++98方法:构造函数私有化

假设A类的构造函数私有化,那么如果B类继承A类,B就无法调用A的构造函数,任何继承A的类都没有办法实例化,这样A就不能不继承。

class InheritBan
{
public:
	static InheritBan GetInstance(int a = 0)
	{
		InheritBan ret(a);
		return ret;
	}

	~InheritBan() { };

private:
	//构造函数私有化
	InheritBan(int a)
		: _a(a)
	{ }

	int _a;
};
  • C++11方法 -- final关键字

final关键字有两种用法:a.修饰类禁止类被继承   b.修饰虚函数禁止虚函数重写

class InheritBan final
{
public:
	// ...

private:
	// ...
};

五. 只能实例化出一个对象的类 -- 单例模式

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

单例模式的设计有两种方法:a.饿汉模式   b.懒汉模式

饿汉模式 -- 构造函数私有化,在程序进入main()函数之前就实例化出对象

构造函数私有化,是为了防止在main()函数中实例化对象。在单例模式singleton中,提供一个静态成员static singleton _inst,这是一个创建在静态区的单例模式对象,存储在静态区的成员_inst,充当了访问这个类的全局接口。

还应当禁止拷贝构造函数和赋值函数的生成,以防止使用_inst拷贝创建另一个singleton类对象。

饿汉模式的优缺点:

  • 优点:a.简单   b.线程安全
  • 缺点:a.当有多个单例类时,无法确定实例化顺序    b.进入main()函数前要实例化多个对象,会降低软件(程序)的启动速度。
class singleton
{
public:
	//提供公有的静态单例模式对象
	static singleton _inst;   //声明

	//防止拷贝
	singleton(const singleton& sl) = delete;
	singleton& operator=(const singleton& sl) = delete;

	//成员函数
	int get() { return _s; }
	void Alloc() { std::cout << "Alloc()" << std::endl; }
	void Delloc() { std::cout << "Delloc()" << std::endl; }

private:
	//构造函数私有化
	singleton(int s = 0)
		: _s(s)
	{ }

	int _s;
};

singleton singleton::_inst(2);   //定义单例模式对象

int main()
{
	//全局都通过_inst来访问singleton类
	std::cout << singleton::_inst.get() << std::endl;
	singleton::_inst.Alloc();
	singleton::_inst.Delloc();
	return 0;
}

懒汉模式 -- 第一次使用对象时实例化对象

将构造函数私有化,同时提供一个公有的静态成员变量singleton* _pinst,设置初值为nullptr,当调用GetInstance函数希望获取对象时,GetInstance函数先判断_pinst是否为空,如果是,将new singleton的返回值赋给_pinst,然后返回_pinst,如果_pinst本身就是nullptr,那么直接返回_pinst。还应当禁止拷贝构造函数和赋值函数的生成。

懒汉模式的优缺点:

  • 优点:a.能够明确单例对象实例化的顺序   b.不会拖慢软件的启动速度
  • 缺点:a.设计复杂   b.确保线程安全相对困难
class singleton
{
public:
	static singleton* _pinst;  //声明指向对象的指针

	//对象获取函数
	static singleton* GetInstance(int s = 0)
	{
		if (_pinst == nullptr)
		{
			_pinst = new singleton(s);
		}

		return _pinst;
	}

	//禁止拷贝
	singleton(const singleton& sg) = delete;
	singleton& operator=(const singleton& sg) = delete;

	//成员函数
	int get() { return _s; }
	void Alloc() { std::cout << "Alloc()" << std::endl; }
	void Delloc() { std::cout << "Delloc()" << std::endl; }

private:
	singleton(int s)
		: _s(s)
	{ }

	int _s;
};

singleton* singleton::_pinst = nullptr;

int main()
{
	//调用成员函数
	singleton::GetInstance()->Alloc();
	singleton::GetInstance()->Delloc();
	return 0;
}

单例对象的资源释放问题

  • 在大部分情况下,单例对象不需要去释放资源,因为单例对象一般不大,且在整个程序运行都有可能会被使用,当程序运行结束时,单例对象也会被释放。
  • 在某些特殊情况下,如析构单例对象时要进行持久化操作(向文件或数据库中写数据)时,就专门定义一个类用于释放单例对象资源,将这个用于资源释放的类在全局上实例化出一个对象,程序结束时调用它的析构函数,它的析构函数释放单例对象的资源。
class singleton
{
public:
	static singleton* _pinst;  //声明指向对象的指针

	//对象获取函数
	static singleton* GetInstance(int s = 0)
	{
		if (_pinst == nullptr)
		{
			_pinst = new singleton(s);
		}

		return _pinst;
	}

	//禁止拷贝
	singleton(const singleton& sg) = delete;
	singleton& operator=(const singleton& sg) = delete;

	//成员函数
	void Alloc() { std::cout << "Alloc()" << std::endl; }
	void Delloc() { std::cout << "Delloc()" << std::endl; }

	//定义垃圾回收内嵌类CGarbo
	//CGarbo是singleton的友元类
	class CGarbo
	{
	public:
		~CGarbo()
		{
			std::cout << "Delete" << std::endl;
			delete _pinst;
		}
	};

private:
	singleton(int s)
		: _s(s)
	{ }

	int _s;
};

singleton* singleton::_pinst = nullptr;
singleton::CGarbo cg;   //创建用于垃圾回收的类对象

int main()
{
	//调用成员函数
	singleton::GetInstance()->Alloc();
	singleton::GetInstance()->Delloc();
	return 0;
}  //gc生命周期结束,调用析构函数清理资源

你可能感兴趣的:(C++从入门到精通,c++,开发语言)