Varias realizaciones del patrón Singleton de C++

El concepto de Patrón Singleton

definición de esquema

Garantiza que solo haya una instancia de una clase y proporciona un punto de acceso global a ella.

Singleton hambriento y Singleton perezoso

El patrón singleton común tiene dos ramas 饿汉单例y 懒汉单例.

  • Singleton hambriento significa que el objeto singleton se crea cuando se inicializa el programa. Su ventaja es que se puede obtener directamente cuando se va a usar el objeto, la desventaja es que no importa si el objeto es llamado o no, se creará para ocupar memoria.
  • El singleton perezoso significa que el objeto correspondiente se crea cuando se llama al objeto singleton por primera vez. Su ventaja es que no se crearán los objetos que no se llamen, la desventaja es que llevará más tiempo crear un objeto, lo que resulta en la particularidad de la primera llamada, y el objeto no es seguro para subprocesos cuando se crea.

Recomendaciones:
Se debe usar un singleton, y la inicialización del objeto singleton se debe ejecutar directamente al comienzo del subproceso principal .
Esto tiene muchas ventajas. Primero, garantiza que no se generarán objetos cuando se crea el objeto 竞争条件(race condition), lo que hace que la escritura del código sea simple y clara (si no se crea en el subproceso principal, varios subprocesos deberían considerar la creación de objetos al mismo tiempo. En este momento, debe restringirse por medios como mutexes. Aunque la palabra clave static de C++ 11 puede garantizar que solo se cree un objeto, todavía no se recomienda usarlo de esta manera);

Implementación del patrón singleton

1. Implementación del modo singleton hombre hambriento más básico

#include <iostream>

class SingletonClass {
    
    
public:
	//单例对象的全局访问点
	static SingletonClass* getInstance() {
    
    
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new SingletonClass();
			return m_pInstance;
		}
		std::cout << "对象的地址为 " << m_pInstance << std::endl;
		return m_pInstance;
	}

	//销毁单例对象
	static void destoryInstance() {
    
    
		delete m_pInstance;  //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
		m_pInstance = nullptr;
		return;
	}

private:
	//构造函数
	SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被创建" << std::endl;
	}

	//析构函数
	~SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被销毁" << std::endl;
	}

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

	static SingletonClass* m_pInstance;  //单例对象的声明
};

SingletonClass* SingletonClass::m_pInstance = nullptr;  //单例对象的定义

int main() {
    
    
	//在主线程最开始的地方就创建出单例对象,相当于饿汉单例
	SingletonClass::getInstance();

	//...

	//多次调用,获取的都是同一个对象
	SingletonClass* obj1 = SingletonClass::getInstance();
	SingletonClass* obj2 = SingletonClass::getInstance();
	SingletonClass* obj3 = SingletonClass::getInstance();

	//...

	//最后要手动销毁单例对象
	SingletonClass::destoryInstance();

	return 0;
}

输出:
SingletonClass的单例对象被创建
对象的地址为 00000172D3499600
对象的地址为 00000172D3499600
对象的地址为 00000172D3499600
SingletonClass的单例对象被销毁

2. Proporcione una interfaz adicional, para que Hungry Singleton pueda reflejarse en el diseño de la interfaz

En 1, aunque lo que queremos lograr es Hungry Singleton, debido a que no se proporciona una interfaz específica, el programador debe asegurarse de que se llame una vez en la función principal para garantizar la corrección lógica, lo que obviamente es inapropiado. Por lo tanto, se puede agregar una interfaz para crear específicamente el objeto Hungry Singleton. Se lanzará una excepción al intentar obtener el objeto Hungry Singleton antes de que se cree el objeto singleton, para recordarle al programador que este es Hungry Singleton y debe crearse en la función principal SingletonClass::getInstance();.

#include <iostream>
#include <exception>

class SingletonClass {
    
    
public:
	//单例对象的全局访问点
	static SingletonClass* getInstance() {
    
    
		if (m_pInstance == nullptr) {
    
    
			throw std::logic_error("this instance is not construct, please construct the instance in main() function first");
		}
		std::cout << "对象的地址为 " << m_pInstance << std::endl;
		return m_pInstance;
	}

	//创建单例对象
	static void constructInstance() {
    
    
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new SingletonClass();
		}
		return;
	}

	//销毁单例对象
	static void destoryInstance() {
    
    
		delete m_pInstance;  //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
		m_pInstance = nullptr;
		return;
	}

private:
	//构造函数
	SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被创建" << std::endl;
	}

	//析构函数
	~SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被销毁" << std::endl;
	}

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

	static SingletonClass* m_pInstance;  //单例对象的声明
};

SingletonClass* SingletonClass::m_pInstance = nullptr;  //单例对象的定义

int main() {
    
    
	//若没有创建单例对象,就尝试获取它,会抛出异常
	try {
    
    
		SingletonClass* obj1 = SingletonClass::getInstance();
	}
	catch (const std::exception& e) {
    
    
		std::cout << e.what() << std::endl;
	}

	//创建单例对象
	SingletonClass::constructInstance();

	//...

	//多次调用,获取的都是同一个对象
	SingletonClass* obj1 = SingletonClass::getInstance();
	SingletonClass* obj2 = SingletonClass::getInstance();
	SingletonClass* obj3 = SingletonClass::getInstance();

	//...

	//最后要手动销毁单例对象
	SingletonClass::destoryInstance();

	return 0;
}

输出:
this instance is not construct, please construct the instance in main() function first
SingletonClass的单例对象被创建
对象的地址为 000001F768EE9600
对象的地址为 000001F768EE9600
对象的地址为 000001F768EE9600
SingletonClass的单例对象被销毁

3. Utilice el mecanismo RAII para realizar la liberación automática de objetos singleton

En 1 y 2, el programador necesita reciclar manualmente el objeto singleton De hecho, bajo ciertas circunstancias, el mecanismo RAII puede usarse para liberar automáticamente el objeto singleton. Dado que el objeto singleton es en realidad un miembro estático de la clase, es necesario utilizar técnicas especiales para administrar la vida del objeto singleton a lo largo del ciclo de vida de un objeto de una clase dentro de una clase.
Tenga en cuenta que el ciclo de vida del objeto estático es coherente con el ciclo de vida del programa, por lo que siempre que el objeto se inicialice al comienzo de la función principal, el objeto singleton se puede obtener normalmente cuando el programa se está ejecutando.
La desventaja de este método es que es imposible decidir cuándo destruir el objeto singleton.

#include <iostream>
#include <exception>

class SingletonClass {
    
    
public:
	//单例对象的全局访问点
	static SingletonClass* getInstance() {
    
    
		if (m_pInstance == nullptr) {
    
    
			throw std::logic_error("this instance is not construct, please construct the instance in main() function first");
		}
		std::cout << "对象的地址为 " << m_pInstance << std::endl;
		return m_pInstance;
	}

	//初始化单例对象
	static void constructInstance() {
    
    
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new SingletonClass();
			static SingletonClass_Helper helper;  //利用此对象的生命周期来自动销毁单例对象
		}
		return;
	}

private:
	//构造函数
	SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被创建" << std::endl;
	}

	//析构函数
	~SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被销毁" << std::endl;
	}

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

	static SingletonClass* m_pInstance;  //单例对象的声明

	//内部类,用于辅助销毁单例对象
	class SingletonClass_Helper {
    
    
	public:
		~SingletonClass_Helper() {
    
    
			delete SingletonClass::m_pInstance;  //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
			m_pInstance = nullptr;
		}
	};
};

SingletonClass* SingletonClass::m_pInstance = nullptr;  //单例对象的定义

int main() {
    
    
	std::cout << "main() start" << std::endl;

	{
    
    
		std::cout << "Scope start" << std::endl;

		//创建单例对象
		SingletonClass::constructInstance();

		//...

		//多次调用,获取的都是同一个对象
		SingletonClass* obj1 = SingletonClass::getInstance();
		SingletonClass* obj2 = SingletonClass::getInstance();
		SingletonClass* obj3 = SingletonClass::getInstance();

		//...

		std::cout << "Scope end" << std::endl;
	}  //出作用域,static SingletonClass_Helper helper;也不会被销毁,因此单例也不会被销毁

	//依然可以获得单例对象
	SingletonClass* obj1 = SingletonClass::getInstance();
	SingletonClass* obj2 = SingletonClass::getInstance();
	SingletonClass* obj3 = SingletonClass::getInstance();

	std::cout << "main() end" << std::endl;

	return 0;
}

输出:
main() start
Scope start
SingletonClass的单例对象被创建
对象的地址为 00000278654695F0
对象的地址为 00000278654695F0
对象的地址为 00000278654695F0
Scope end
对象的地址为 00000278654695F0
对象的地址为 00000278654695F0
对象的地址为 00000278654695F0
main() end
SingletonClass的单例对象被销毁

4. C ++ 11 usa el modo singleton perezoso implementado por objetos locales estáticos

Para el singleton perezoso, es necesario considerar la situación de que el punto de acceso global se llama simultáneamente bajo múltiples subprocesos, pero el objeto singleton no se ha creado (es decir, la primera vez que se llama). En este caso, el objeto singleton se puede crear dos veces. La lógica es la siguiente:
inserte la descripción de la imagen aquí

  • Código singleton perezoso con problemas de seguridad de subprocesos:
#include <iostream>
#include <thread>

class SingletonClass {
    
    
public:
	//这样的接口实际是一个懒汉单例
	static SingletonClass* getInstance() {
    
    
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new SingletonClass();
		}
		std::cout << "单例对象的地址为 " << m_pInstance << std::endl;
		return m_pInstance;
	}

	//销毁单例对象
	static void destoryInstance() {
    
    
		delete m_pInstance;  //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
		m_pInstance = nullptr;
		return;
	}
private:
	//构造函数
	SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被创建" << std::endl;
	}

	//析构函数
	~SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被销毁" << std::endl;
	}

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

	static SingletonClass* m_pInstance;  //static data member,其本质是global object
};

SingletonClass* SingletonClass::m_pInstance = nullptr;

//线程初始化函数
void func() {
    
    
	SingletonClass::getInstance();
	return;
}

int main() {
    
    
	//多个线程同时去尝试创建单例对象
	std::thread th1(func);
	std::thread th2(func);

	th1.join();
	th2.join();

	//最后要手动销毁单例对象
	SingletonClass::destoryInstance();

	return 0;
}

可能的输出:
SingletonClass的单例对象被创建
单例对象的地址为 000001750F380410
SingletonClass的单例对象被创建
单例对象的地址为 000001750F3802B0
SingletonClass的单例对象被销毁

Antes de C++ 11, este problema generalmente se solucionaba agregando un mutex. Por supuesto, esto hará que la escritura de código sea más problemática. La lógica en este momento es la siguiente:
inserte la descripción de la imagen aquí

  • Código singleton perezoso que usa mutexes para garantizar la seguridad de subprocesos (aquí usa la técnica de doble bloqueo):
#include <iostream>
#include <thread>
#include <mutex>

class SingletonClass {
    
    
public:
	//这样的接口实际是一个懒汉单例
	static SingletonClass* getInstance() {
    
    
		if (m_pInstance == nullptr) {
    
      //双重锁定 A
			std::unique_lock<std::mutex> my_unique_lock(m_mutex);   //自动上锁解锁
			if (m_pInstance == nullptr) {
    
      //双重锁定 B
				m_pInstance = new SingletonClass();
			}
		}
		std::cout << "单例对象的地址为 " << m_pInstance << std::endl;
		return m_pInstance;
	}

	//销毁单例对象
	static void destoryInstance() {
    
    
		delete m_pInstance;  //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
		m_pInstance = nullptr;
		return;
	}
private:
	//构造函数
	SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被创建" << std::endl;
	}

	//析构函数
	~SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被销毁" << std::endl;
	}

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

	static SingletonClass* m_pInstance;  //static data member,其本质是global object
	static std::mutex m_mutex;  //互斥量
};

SingletonClass* SingletonClass::m_pInstance = nullptr;
std::mutex SingletonClass::m_mutex;

//线程初始化函数
void func() {
    
    
	SingletonClass::getInstance();
	return;
}

int main() {
    
    
	//多个线程同时去尝试创建单例对象
	std::thread th1(func);
	std::thread th2(func);

	th1.join();
	th2.join();

	//最后要手动销毁单例对象
	SingletonClass::destoryInstance();

	return 0;
}

输出:
SingletonClass的单例对象被创建
单例对象的地址为 0000027A7A2502C0
单例对象的地址为 0000027A7A2502C0
SingletonClass的单例对象被销毁

El estándar C++11 garantiza que si varios subprocesos intentan inicializar el mismo objeto local estático al mismo tiempo , la inicialización ocurre estrictamente una vez. Referencia
Cabe señalar que lo que se garantiza aquí es un objeto local estático , pero no una variable miembro estática , por lo que el modo singleton implementado en 1, 2 y 3 no es seguro para subprocesos cuando se crean objetos (por supuesto, debido a que los diseños de 1, 2 y 3 son todos singleton hambrientos, este problema no existe), porque el objeto singleton es static SingletonClass* m_pInstance;.

  • Código Lazy Singleton implementado usando objetos locales estáticos de C++ 11:
#include <iostream>
#include <thread>

class SingletonClass {
    
    
public:
	//这样的接口实际是一个懒汉单例
	static SingletonClass& getInstance() {
    
    
		static SingletonClass instance;  //function-local static object
		return instance;
	}

private:
	//构造函数
	SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被创建" << std::endl;
	}

	//析构函数
	~SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被销毁" << std::endl;
	}

	//禁止拷贝
	SingletonClass(const SingletonClass&) = delete;
	SingletonClass operator=(const SingletonClass&) = delete;
};

//线程初始化函数
void func() {
    
    
	SingletonClass::getInstance();
	return;
}

int main() {
    
    
	//多个线程同时去尝试创建单例对象
	std::thread th1(func);
	std::thread th2(func);

	th1.join();
	th2.join();

	return 0;
}

输出:
SingletonClass的单例对象被创建
SingletonClass的单例对象被销毁

También es muy simple cambiar el singleton perezoso por el singleton hambriento, solo asegúrese de obtener el objeto singleton una vez en la función principal.

Resumen de las implementaciones 1, 2, 3 y 4

Después del estándar C ++ 11, la implementación de 4 es más popular y, en general, se puede usar como 4.
Pero la implementación de 2 también es muy buena, el diseño de la interfaz es claro y el modo de hombre hambriento es una forma recomendada de usarlo.

5. Use plantillas para implementar un modo singleton hombre hambriento más general

Antes de C++11, los parámetros de las plantillas eran fijos, por lo que la práctica común era adaptarse solo a constructores con menos de 6 parámetros.
el código se muestra a continuación:

#include <iostream>

template <typename T>  //模板参数应当是一个类类型
class Singleton {
    
    
public:
	//单例对象的全局访问点
	//因为不需要参数了,所以可以设计的很简单
	static T* getInstance() {
    
    
		if (m_pInstance == nullptr) {
    
      //单例对象未创建,说明代码逻辑存在问题
			throw std::logic_error("this instance is not construct, please construct the instance in main() function first");
		}
		std::cout << "单例对象的地址为 " << m_pInstance << std::endl;
		return m_pInstance;
	}

	//创建单例对象 支持0个参数
	static void constructInstance() {
    
    
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new T();
		}
		return;
	}
	
	//创建单例对象 支持1个参数
	template <typename T0>
	static void constructInstance(T0 arg0) {
    
    
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new T(arg0);
		}
		return;
	}

	//创建单例对象 支持2个参数
	template <typename T0, typename T1>
	static void constructInstance(T0 arg0, T1 arg1) {
    
    
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new T(arg0, arg1);
		}
		return;
	}

	//创建单例对象 支持3个参数
	template <typename T0, typename T1, typename T2>
	static void constructInstance(T0 arg0, T1 arg1, T2 arg2) {
    
    
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new T(arg0, arg1, arg2);
		}
		return;
	}

	//创建单例对象 支持4个参数
	template <typename T0, typename T1, typename T2, typename T3>
	static void constructInstance(T0 arg0, T1 arg1, T2 arg2, T3 arg3) {
    
    
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new T(arg0, arg1, arg2, arg3);
		}
		return;
	}

	//创建单例对象 支持5个参数
	template <typename T0, typename T1, typename T2, typename T3, typename T4>
	static void constructInstance(T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
    
    
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new T(arg0, arg1, arg2, arg3, arg4);
		}
		return;
	}

	//创建单例对象 支持6个参数
	template <typename T0, typename T1, typename T2, typename T3, typename T4, typename T5>
	static void constructInstance(T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) {
    
    
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new T(arg0, arg1, arg2, arg3, arg4, arg5);
		}
		return;
	}

	//销毁单例对象
	static void destoryInstance() {
    
    
		delete m_pInstance;  //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
		m_pInstance = nullptr;
		return;
	}

private:
	Singleton() = default;
	~Singleton() = default;
	//禁止拷贝
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
	static T* m_pInstance;
};

template <typename T>
T* Singleton<T>::m_pInstance = nullptr;

//测试部分
//类A,构造函数无参
struct A {
    
    
	A() {
    
    
		std::cout << "A类对象被创建" << std::endl;
	}

	~A() {
    
    
		std::cout << "A类对象被销毁" << std::endl;
	}
};

//类B,构造函数有参数
struct B {
    
    
	B(int x, int y, int z) {
    
    
		std::cout << "B类对象被创建" << std::endl;
	}

	~B() {
    
    
		std::cout << "B类对象被销毁" << std::endl;
	}
};

int main() {
    
    
	//创建单例对象
	Singleton<A>::constructInstance();  //创建A类对象单例
	Singleton<B>::constructInstance(1, 2, 3);  //创建B类对象单例

	//获取单例对象
	auto obj_a1 = Singleton<A>::getInstance();
	auto obj_a2 = Singleton<A>::getInstance();

	auto obj_b1 = Singleton<B>::getInstance();
	auto obj_b2 = Singleton<B>::getInstance();

	//销毁单例对象
	Singleton<A>::destoryInstance();
	Singleton<B>::destoryInstance();

	return 0;
}

输出:
A类对象被创建
B类对象被创建
单例对象的地址为 000001BA054D02E0
单例对象的地址为 000001BA054D02E0
单例对象的地址为 000001BA054D0400
单例对象的地址为 000001BA054D0400
A类对象被销毁
B类对象被销毁

Después de C ++ 11, se introdujo 可变长模板、万能引用、完美转发y, utilizando estas tecnologías, se pueden escribir plantillas muy generales.
el código se muestra a continuación:

#include <iostream>

template <typename T>  //模板参数应当是一个类类型
class Singleton {
    
    
public:
	//单例对象的全局访问点
	//因为不需要参数了,所以可以设计的很简单
	static T* getInstance() {
    
    
		if (m_pInstance == nullptr) {
    
      //单例对象未创建,说明代码逻辑存在问题
			throw std::logic_error("this instance is not construct, please construct the instance in main() function first");
		}
		std::cout << "单例对象的地址为 " << m_pInstance << std::endl;
		return m_pInstance;
	}

	//创建单例对象 可以传入任意参数
	template <typename... Args>
	static void constructInstance(Args&&... args) {
    
      //Args&&... args是万能引用
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new T(std::forward<Args>(args)...);  //完美转发
		}
		return;
	}

	//销毁单例对象
	static void destoryInstance() {
    
    
		delete m_pInstance;  //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
		m_pInstance = nullptr;
		return;
	}

private:
	Singleton() = default;
	~Singleton() = default;
	//禁止拷贝
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
	static T* m_pInstance;
};

template <typename T>
T* Singleton<T>::m_pInstance = nullptr;

//测试部分
//类A,构造函数无参
struct A {
    
    
	A() {
    
    
		std::cout << "A类对象被创建" << std::endl;
	}

	~A() {
    
    
		std::cout << "A类对象被销毁" << std::endl;
	}
};

//类B,构造函数有参数
struct B {
    
    
	B(int x, int y, int z) {
    
    
		std::cout << "B类对象被创建" << std::endl;
	}

	~B() {
    
    
		std::cout << "B类对象被销毁" << std::endl;
	}
};

//类C,有带左值和带右值的构造函数
struct C {
    
    
	C(const std::string&) {
    
    
		std::cout << "C类对象被创建,参数为左值" << std::endl;
	}

	C(std::string&&) {
    
    
		std::cout << "C类对象被创建,参数为右值" << std::endl;
	}

	~C() {
    
    
		std::cout << "C类对象被销毁" << std::endl;
	}
};

int main() {
    
    
	//创建单例对象
	Singleton<A>::constructInstance();  //创建A类对象单例
	Singleton<B>::constructInstance(1, 2, 3);  //创建B类对象单例
	std::string c_arg = "hello";
	Singleton<C>::constructInstance(std::move(c_arg));  //创建C类对象单例

	//获取单例对象
	auto obj_a1 = Singleton<A>::getInstance();
	auto obj_a2 = Singleton<A>::getInstance();

	auto obj_b1 = Singleton<B>::getInstance();
	auto obj_b2 = Singleton<B>::getInstance();

	auto obj_c1 = Singleton<C>::getInstance();
	auto obj_c2 = Singleton<C>::getInstance();

	//销毁单例对象
	Singleton<A>::destoryInstance();
	Singleton<B>::destoryInstance();
	Singleton<C>::destoryInstance();

	return 0;
}

输出:
A类对象被创建
B类对象被创建
C类对象被创建,参数为右值
单例对象的地址为 0000019BE71E0340
单例对象的地址为 0000019BE71E0340
单例对象的地址为 0000019BE71E03B0
单例对象的地址为 0000019BE71E03B0
单例对象的地址为 0000019BE71E0380
单例对象的地址为 0000019BE71E0380
A类对象被销毁
B类对象被销毁
C类对象被销毁

Supongo que te gusta

Origin blog.csdn.net/weixin_43003108/article/details/127106390
Recomendado
Clasificación