C++ implementa una clase especial desde la rutina de la entrevista hasta la implementación del patrón singleton

contenido

prefacio

1. Cómo diseñar una clase que solo pueda crear objetos en el montón

2. Cómo diseñar una clase que solo pueda crear objetos en la pila

3. Cómo diseñar una clase que no se pueda copiar

4. Cómo diseñar una clase que no se puede heredar

5. Patrón Singleton desde la teoría hasta la implementación

6. Resume el artículo anterior


prefacio

En la entrevista, a menudo encontramos algunas preguntas especiales de diseño de clase. Estas preguntas en realidad implican algunos patrones de diseño. Si queremos restringir la construcción de objetos, la forma más fácil de pensar es, por supuesto, restringir primero el constructor, y luego lo haremos. proporcionar especial La interfaz para la construcción de objetos.Al igual que el patrón singleton. . . . .  

Sin embargo, antes de construir un objeto, no tenemos un objeto, así que, ¿cómo podemos llamar a la función restrictiva que diseñamos para crear un objeto? Haga que la función sea estática, de modo que esta función pertenezca a toda la clase, use el nombre de clase + :: se puede acceder

  • Constructor privado + prohibir la construcción de copias, no dejar que copie la construcción
  • Formas de deshabilitar la construcción de copias: 1. Construcción de copias privadas (C++98) 2. Eliminar modificación de palabras clave (C++11)
  • El constructor está privatizado, pero aún necesita poder construir un objeto, proporcionando la interfaz pública GetInstance para restringir la creación de objetos.
  • La interfaz pública es estática por lo que se puede acceder a través del nombre de la clase (resuelva el problema de cómo llamar)

1. Cómo diseñar una clase que solo pueda crear objetos en el montón

  • constructor privado
  • Deshabilitar la construcción de copias
  • Proporciona funciones de interfaz estática pública de clase que están restringidas y solo se pueden crear en el montón
class HeapOnly {
public:
	static HeapOnly* CreateObj() {
		return new HeapOnly;
	}
private:
	HeapOnly() {};	
	//方式1: 防止拷贝构造
	HeapOnly(const HeapOnly& h);
public:
	//方式2 :防止拷贝构造
	HeapOnly(const HeapOnly& h) = delete;
};

2. Cómo diseñar una clase que solo pueda crear objetos en la pila

  • Solo se puede crear en la pila, de hecho, está prohibido soltar objetos en el montón y creados por el segmento de datos globales
  • Además de usar el método anterior, la privatización y luego proporcionar una interfaz GetInstance, también existen los siguientes métodos
    	void* operator new(size_t size) = delete;
    	void operator delete(void* p) = delete;

Los recuerdos matan La esencia de cuando conocemos un objeto:

  • Llame al operador global nuevo para asignar memoria
  • Llame al constructor para inicializar

La esencia de eliminar un objeto:

  • Llame al destructor para que realice el procesamiento final, que puede ser escribir en el disco, cerrar el archivo, etc.
  • Llame al operador global delete para liberar el recurso
class StackOnly {
public:
	static StackOnly CreateObj() {
		return StackOnly();		//构造一个临时对象拷贝返回
	}
    //因为存在临时对象的返回, 存在拷贝构造, 所以没有必要也不可以禁止掉拷贝构造。。
    //思考 ? 和上述堆区创建对象不一样之处
private:
	StackOnly() {};
    //方式2: 禁止堆区构造必须调用的函数
	//void* operator new(size_t size) = delete;
	//void operator delete(void* p) = delete;
};
  • Se prohibe el defecto de usar oprator new y operator delete, solo restringe la creación de objetos en el área de heap , pero en el segmento de datos global, es decir, no está prohibido estático estático ..

3. Cómo diseñar una clase que no se pueda copiar

class CopyBan {
public:
	CopyBan(int a = 0): _a(a) {
    //C++11的方式
	//CopyBan(const CopyBan& obj) = delete;
	//CopyBan& operator= (const CopyBan& obj) = delete;
private:
	//C++98方式
	CopyBan(const CopyBan& obj);
	CopyBan& operator= (const CopyBan& obj);
	int _a;
};

4. Cómo diseñar una clase que no se puede heredar

  • Método 1: haga que el constructor de la clase principal sea privado, de modo que la herencia no informe un error, pero la subclase no puede llamar al constructor de la clase principal y, por lo tanto, no puede reutilizar el código de la clase principal. . . Se informa un error al crear instancias... Porque solo cuando la creación de instancias llamará realmente al constructor de la clase principal, descubrirá que no se puede llamar.    
  • Método 2: en el método C++11, se usa una palabra clave final para modificar la clase, lo que indica que esta clase es la clase final y, naturalmente, no se permitirá que se herede, y se informará un error una vez que sea heredado.
//方式1, 
class NonInherit
{
public:
	static NonInherit GetInstance()
	{
		return NonInherit();
	}
private:
	NonInherit()
	{}
};

class B : public NonInherit {
	
};

//方式2:使用final 关键字修饰
class A final {	    
};

5. Patrón Singleton desde la teoría hasta la implementación

Qué es el patrón singleton: un singleton, un solo objeto (instancia), es decir, una clase solo puede crear un objeto, este es el patrón singleton.

Los escenarios de aplicación simples del patrón singleton son los siguientes:

  • Por ejemplo, en un programa de servidor, la información de configuración del servidor se almacena en un archivo, y un objeto singleton lee uniformemente los datos de configuración, y luego otros objetos en el proceso de servicio obtienen la información de configuración a través del objeto singleton. forma simplifica la gestión de la configuración en entornos complejos.
  • También está el mapa durante el desarrollo del juego, que generalmente se diseña como una sola instancia, porque el mapa solo debe cargarse con una copia y todos los jugadores usan el mismo mapa.

Ideas de implementación de patrones Singleton:

  • En primer lugar, debido a nuestras restricciones sobre la creación de objetos, aún debemos privatizar el constructor y prohibir la construcción de copias, y luego debemos proporcionar una interfaz externa GetInstance para obtener el objeto singleton global.
  • Entonces, debido a que solo hay un objeto único a nivel mundial, una clase solo puede instanciar un objeto. La fuente de este objeto es: área de montón + segmento de datos globales (no debe ser el área de pila, porque el ciclo de vida del área de pila es limitado )
  • Este objeto debe estar con la clase, debe pertenecer a toda la clase, no a un objeto, por lo que necesita estática 

Estilo chino hambriento: tengo que comer tan pronto como llegue, por lo que este objeto único se ha creado antes de ingresar a la función principal....

Use el área de montón nuevo un objeto + puntero de miembro estático para apuntar al nuevo objeto de área de montón

class Singleton {
public:
	static Singleton* GetInstance() {
		return _ins;
	}
	void Print() {
		cout << "饿汉子" << endl;
	}
private:
	Singleton() {}
	Singleton(const Singleton& ins) = delete;	//禁止拷贝构造
	Singleton& operator=(const Singleton& ins) = delete;
	static Singleton* _ins;
};

Singleton* Singleton::_ins = new Singleton;

Otra forma muy sencilla es crear directamente un objeto estático que devuelva &ins; de una forma muy molesta, usando las ventajas de lo estático, solo crea este objeto por primera vez, y este objeto no se usará una vez Después de que se libera, el mismo objeto se reutilizado cada vez, y el singleton también se implementa de manera inteligente y simple. . .

  • Desventajas:  El objeto singleton está en el área estática.Si el objeto singleton es demasiado grande, no es muy bueno y no es adecuado.
  • Y no hay forma de controlar activamente la liberación del objeto singleton.
class Singleton {
public:
	static Singleton* GetInstance() {
		static Singleton ins;
		return &ins;
	}
private:
	Singleton() {}
	Singleton(const Singleton& ins) = delete;
	const Singleton& operator =(const Singleton& ins) = delete;
};

Implementación perezosa de singleton:

En comparación con el estilo Hungry Man, ¿cuándo es apropiado el estilo Lazy Man?

  • Suponiendo que se debe realizar una gran cantidad de trabajo de inicialización de configuración en el constructor de clase singleton, entonces el hombre hambriento no es adecuado. . (Provocará un retraso al ingresar a la función principal y el inicio del programa será muy lento, porque el hombre hambriento es un objeto creado antes de la función principal)

Hombre perezoso que significa:   

        El estilo perezoso, como su nombre lo indica, es perezoso. No creará un objeto singleton antes de ingresar a la función principal, sino que solo creará un objeto singleton cuando sea necesario por primera vez . . . Significa que las operaciones como la creación de la configuración de objetos, etc., deben implementarse en GetInstance. . . . .    

¿Pensando en una pregunta en este momento??????? Para el estilo perezoso, crear un objeto al llamar a la función GetInstance es en realidad equivalente a una operación de escritura, ¿habrá un problema de inseguridad del subproceso????   

Por supuesto, no hay problema en llamar a la función GetInstance en un área de subprocesos, pero si varios subprocesos la llaman al mismo tiempo, puede haber conflictos.... Reentrada, dos subprocesos o múltiples subprocesos entran en el juicio en el mismo tiempo _ins == nullptr No es seguro crear objetos... Entonces, en este momento, necesitamos vincularlo como una operación atómica para el juicio y la creación de _ins

  • Explícito: las operaciones de subprocesos múltiples que leen recursos críticos son seguras para subprocesos
  • No es seguro para subprocesos cuando los subprocesos escriben simultáneamente en recursos críticos.

No hay problema de peligro de subprocesos cuando varios subprocesos realizan operaciones de lectura al mismo tiempo, es decir, reentrada, siempre que no se escriban datos, no hay problema, por lo que la holgura en el frente no necesita protección de bloqueo, porque él solo devuelve El objeto creado es solo una operación de lectura, no hay una operación de escritura que necesite crear un objeto, por lo que incluso si es de subprocesos múltiples para leer, no hay problema... Aquí es necesario proteger la escritura de subprocesos múltiples ..... ..

 Pero, ¿hay algún problema de eficiencia en lo anterior???? Si hay muchos subprocesos que necesitan llamar a esta función GetInstance, pero de hecho, habrá problemas de seguridad de subprocesos solo cuando se escribe por primera vez.,... . .porque una vez que la primera escritura es segura para subprocesos, lo único que debe hacer después de escribir es la operación de lectura, y no necesita escribir más. En este momento, solo necesita experimentar la primera escritura. es seguro para subprocesos, y todas las siguientes operaciones son operaciones de lectura. De hecho, no hay necesidad de seguir bloqueando y desbloqueando.... Entonces, en este momento, generalmente usamos la doble verificación para mejorar la eficiencia.

class Singleton {
public:
	static Singleton* GetInstance() {
		if (_ins == nullptr) {			//双检查提高效率
			_lock.lock();
			if (_ins == nullptr) {
				_ins = new Singleton;
			}
			_lock.unlock();
		}
		return _ins;
	}

	static void DelInstance() {	//销毁单例对象的接口
		_lock.lock();
		if (_ins) {
			delete _ins;
			_ins = nullptr;
		}
		_lock.unlock();
	}
	void Print() {
		cout << "懒汉子" << endl;
	}
	class Garbage {	//垃圾回收类, 最后前面没有调用Del接口, 此处自动回收
	public:	
		~Garbage() {//此处不进行加锁, 因为此处的锁很可能已经释放
			if (_ins) {
				delete _ins;
				_ins = nullptr;
			}	
		}
		
	};
private:
	Singleton() {
		//配置初始化
	}
	~Singleton() {
		//程序结束之后需要做一些持久化保存工作, 比如写入磁盘操作
	}
	Singleton(const Singleton& ins) = delete;	//禁止拷贝构造
	Singleton& operator=(const Singleton& ins) = delete;
	static Singleton* _ins;
	static mutex _lock;
	static Garbage gar;
};
Singleton* Singleton::_ins = nullptr;
mutex Singleton::_lock;
Singleton::Garbage Singleton::gar;

6. Resume el artículo anterior

  • El primero es introducir desde el diseño de una clase especial:  método universal: constructor privado, y luego proporcionar una interfaz estática pública y restrictiva para crear objetos, porque no hay ningún objeto al principio, después de estático, puede usar el nombre de la clase llamar a la creación
  • Luego, introduzca el patrón singleton: un patrón de diseño en el que una clase solo puede instanciar un objeto... Para lograr la restricción de los objetos instanciados, siguen siendo los constructores privados los que brindan creación restringida o acceso estático público a la función de interfaz de objetos.
  • Singleton de estilo chino hambriento, el objeto singleton se crea tan pronto como llega y se crea antes de ingresar a la función principal. El defecto, si el archivo de configuración es demasiado grande, puede hacer que el programa se inicie muy lentamente. Su función GetInstance solo lee La operación de tomar el objeto singleton _ins, por lo que es seguro para subprocesos y no requiere protección de bloqueo
  • Lazy singleton, cree un objeto singleton en GetInstance cuando sea necesario por primera vez, porque puede haber varias operaciones de escritura de subprocesos cuando se crea una instancia del objeto singleton por primera vez, es decir, varios subprocesos deben llamar a GetInstance, esta vez en orden para evitar conflictos como la reentrada de funciones, la operación atómica está bloqueada para protección Para mejorar la eficiencia, solo se aplica la primera protección de bloqueo de escritura, por lo que se utiliza el modo de verificación doble para mejorar la eficiencia .

Supongo que te gusta

Origin blog.csdn.net/weixin_53695360/article/details/122917855
Recomendado
Clasificación