C++ 特殊类设计

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

如果只能在堆上创建对象,就是限制在栈上和静态区中创建对象,步骤如下:

方法一:将构造函数私有化.

  • 将构造函数设置为私有,以防用户在栈上或者静态区直接调用构造函数创建对象.
  • 在公有区域提供一个静态的成员函数,在该静态成员函数中使用new创建对象,此时因为new在成员函数中,可以调用私有区域的构造函数.
  • 限制外部利用拷贝构造在栈上创建对象,我们可以采用C++11中的delete关键字来限制编译器默认自动产生拷贝构造函数,
class HeapOnly
{
    
    
public:
	//方法一:提供一个获取对象的接口,并且该接口必须设置为静态成员函数
	static HeapOnly* CreateObj()
	{
    
    
		return new HeapOnly;
	}
	void Delete()
	{
    
    
		cout << "Delete()" << endl;
		delete this;
	}
	//限制使用拷贝构造,赋值拷贝.
	HeapOnly(const HeapOnly& hp) = delete;
	HeapOnly& operator=(const HeapOnly& hp) = delete;
private:
	HeapOnly()
		:_a(0)
	{
    
    }
	int _a;
	//C++11
	//HeapOnly(const HeapOnly&) = delete;
};
int main()
{
    
    
	//限制在栈上创建.
	/*HeapOnly hp1;
	
	//限制在静态区创建.
	static HeapOnly hp2;*/

	HeapOnly* ptr = HeapOnly::CreateObj();

   ptr->Delete();
}

注意:

  • 我们提供的CreateObj()函数一定要设置为静态函数,因为非静态成员必须通过对象才能调用,而创建对象又必须通过CreateObj()函数创建.(以下写法类似)

方法二:将析构函数私有化

该方法又分为两种:
1.使用静态成员函数调用析构.
2:公有写Delete()函数,利用this指针析构.

class HeapOnly
{
    
    
public:
	static void Delete(HeapOnly* ptr)
	{
    
    
		cout << "static Delete " << endl;
		delete ptr;
	}
	void Delete()
	{
    
    
		cout << "Delete()" << endl;
		delete this;
	}
private:
	~HeapOnly()
	{
    
    }
	int _a;
};
int main()
{
    
    
	//HeapOnly hp1;
	
	HeapOnly* ptr = new HeapOnly;
    //1.
    HeapOnly::Delete(ptr);
    //2.
	ptr->Delete();
}

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

只能在栈上创建对象,就是限制在静态区和堆上创建对象.思路和上一个设计一样;

  • 将构造函数设置为私有,以外部在堆或者静态区中直接调用构造函数创建对象.
  • 在公有区域提供一个静态的成员函数,在该静态成员函数中以在栈区的形式创造对象并返回,因为在类中不受私有区域的限制,可以访问类的构造函数.

class StackOnly
{
    
    
public:
	//2、提供一个获取对象的接口,并且该接口必须设置为静态成员函数
	static StackOnly CreateObj()
	{
    
    
		StackOnly st;
		return st;
     }
	//不可以限制拷贝构造.
	//StackOnly(const StackOnly& st) = delete;
	//StackOnly& operator=(const StackOnly& st) = delete;
private:
	//1、将构造函数设置为私有
	StackOnly()
		:_a(0)
	{
    
    }
	int _a;
};

该方法的缺点就是无法防止外部调用编译器默认生成的拷贝构造函数创建对象.

StackOnly st1 = StackOnly::CreateObj();
static StackOnly st2(st1); //在静态区中拷贝构造对象st1
StackOnly* ptr = new StackOnly(st1); //在堆上拷贝构造对象st1

注意:

  • 不可以限制拷贝构造,因为CreateObj函数中创建对象为临时对象,因此必须使用传值返回,而传值拷贝一定会调用拷贝构造.

针对于在堆上拷贝构造对象,我们可以采用限制new的方式.

class StackOnly
{
    
    
public:
	//2、提供一个获取对象的接口,并且该接口必须设置为静态成员函数
	static StackOnly CreateObj()
	{
    
    
		StackOnly st;
		return st;
     }
     //限制nwe.
    void* operator new(size_t n) = delete;
private:
	//1、将构造函数设置为私有
	StackOnly()
		:_a(0)
	{
    
    }
	int _a;
};

解释:
因为new创建对象主要分为两步:
1.调用operator new 函数创建空间.
2.调用构造函数完成对对象的初始化.

综合以上解释,我们将operator new进行删除,就是限制了new的使用,进而限制了外部利用拷贝构造在堆中创建对象了,但是我们依旧无法解决如何在静态区中利用拷贝构造创建对象.(一般在实际中也很少遇见).

设计一个不能被拷贝的类

如果想让一个类禁止拷贝,我们只需要让该类不能调用拷贝构造以及赋值运算符重载即可.

  • 在C++98中,将拷贝构造函数与赋值重载只声明不定义,并将访问权限设置为私有.
  • 而在C++11中,通常在默认成员函数中加入跟上=delete,就代表让编译i其删除掉该默认成员函数.
class CopyBan
{
    
    
public:
	CopyBan()
	{
    
    }
private:
	//C++98
	CopyBan(const CopyBan&);
	CopyBan& operator=(const CopyBan&);
	//C++11
	//CopyBan(const CopyBan&) = delete;
	//CopyBan& operator=(const CopyBan&) = delete;
};

设计一个不能被继承的类

C++98

在C++98中,将该类的构造函数设置为私有,因为如果派生类继承了基类的私有成员,就意味着必须调用基类的构造函数对基类的私有成员初始化,但是基类的构造函数设置为私有,就意味派生类无法访问到基类的构造函数,所以该类被继承后也就无法创建出对象.

class NonInherit
{
    
    
public:
	static NonInherit CreateObj()
	{
    
    
		return NonInherit();
	}
private:
	//将构造函数设置为私有
	NonInherit()
	{
    
    }
};

C++11

因为C++98中将基类的构造函数设置为私有而导致无法被派生类继承实际上依旧可可以编译成功,不能将派生类实例化.于是,C++11中新增了final关键字,使用final修饰类表示该类无法被继承,如果强行继承了,也无法编译成功.`

//基类
class Base  final
{
    
    
private:
	Base()
		:_a(0)
	{
    
    }
protected:
	int _a;
};
//派生类
class Derived : protected Base
{
    
    
public:
//
protected:
	int _a;
	int _b;
};

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

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

单例模式有两种实现模式:

饿汉模式

饿汉模式指的是:无论用户将来使用或者不使用,当程序启动时就创建一个唯一的实例对象,简单来说,就是在main函数之前就创建了对象.

饿汉模式的实现步骤如下:

  • 将构造函数设置为私有,并将拷贝构造和运算符重载删除,防止外部利用拷贝构造创建对象.
  • 提供一个指向单例对象的一个静态指针,在main函数之前完成对单例对象的初始化.
  • 使用静态成员函数获取单例对象,也称为全局访问点.
 class Singleton
{
    
    
public:
	static Singleton* GetInstance()
	{
    
    
		return _pList;
	}
	void* Aoll()
	{
    
    
		cout << "Aoll" << endl;
		return _pinst;
	}
	void Dell(void* ptr)
	{
    
    
		cout << "Dell" << endl;
	}
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
private:
	MemoryPool()
	{
    
    //}
	//静态成员.
	static MemoryPool* _pinst;
};
//类外定义
MemoryPool* MemoryPool::_pinst = new MemoryPool;

int main()
{
    
    
	MemoryPool* ptr1 = MemoryPool::GetInstance();
    //通过对象指针调用函数.
	ptr1->Dell(ptr1);
}

注意:
静态成员定义时就是在main函数之前就定义好了,所以我们必须通过类中静态成员函数将该类唯一的实例对象在外部拿到.

优点:简单,没有线程安全问题.

缺点:

  1. 一个程序中,多个单例,并且有先后创建初始化顺序要求时.饿汉模式无法控制,因为静态成员谁先初始化谁后初始化不能确定.比如,程序两个单例类A和B,假设要求A先创建初始化,B再创建初始化.

  2. 饿汉单例类,初始化时任务多,会影响程序启动速度.

懒汉模式

懒汉模式指: 只有第一次使用实例对象时,才创建对象.

懒汉模式实现步骤如下:

  • 将构造函数设置为私有,并将拷贝构造和运算符重载删除,防止外部利用拷贝构造创建对象.

  • 提供一个指向单例对象的static指针,并在程序入口之前先将其初始化为空。

  • 使用静态成员函数获取单例对象,也称为全局访问点.如果为static 指针为空,就说明第一次使用该实例对象需要重新创建获取,如果不为空,则依旧返回原来的实例对象.

class Singleton
{
    
    
public:
	//da
	static Singleton* GetInstance()
	{
    
    
		if (_pList == nullptr)
		{
    
    
			return _pList = new  Singleton;
		}
		return _pList;
	}
	void* Aoll()
	{
    
    
		cout << "Aoll" << endl;
		return _pList;
	}
	void Dell(void* ptr)
	{
    
    
		cout << "Dell" << endl;
	}
private:
	Singleton()
	{
    
    
	}
	static  Singleton* _pList;
};
Singleton* Singleton::_pList = nullptr;

优点:

  • 可以控制初始化顺序,比如一个类中有多个关于懒汉模式的类的内置成员, 我们可以先对内置成员_a调用getinstance,再对内置成员_b调用getInstance,进而让_a先初始化,_b后初始化.

  • 不会影响程序启动速度,因为内置成员后初始化.

缺点:

  • 相对复杂(线程安全问题),这个以后我们深度学习.

单例模式对象释放问题

1: 一般情况下,单例对象不需要释放的,一般整个程序运行期间都会用到他.

2: 单例对象在进程正常结束后,也会资源释放.(除非因为程序错误变为僵尸进程).

3: 有些特殊场景需要释放,比如单例对象析构时,要进行一些持久化操作((将内中的数据写进文件,数据库写).

4: 如果我们直接通过GetInstance()获取实例对象指针然后使用delete删除,这样不仅丑陋,还很容易被忽视.再接下来中我们无法确定外部是否会再调用GetInstance()获取该指针.

所以,针对于单例对象,我们常用了内嵌类的形式,让程序结束时符合相关条件自动销毁.

//垃圾回收类
class CGarbo
{
    
    
public:
	~CGarbo()
	{
    
    
		if (_inst != nullptr)
		{
    
    
			delete _inst;
			_inst = nullptr;
		}
	}
};
//定义一个静态成员变量,在程序结束时,系统会自动调用它的析构函数,从而释放单例对象.
static CGarbo Garbo;

注意:
我们必须通过内嵌类的形式,析构单例类,而不能在单例类的析构函数中使用delete进行资源释放,因为delete又会调用单例类的析构函数,而析构函数中又是使用delete进行资源释放,如此递归删除.

猜你喜欢

转载自blog.csdn.net/m0_63300413/article/details/131284302
今日推荐