Compartir tecnología

Experimente el poder de los punteros inteligentes

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina


Catálogo de cursos



1. Conocimientos básicos de punteros inteligentes.

Desventajas de los punteros sin formato:

  1. olvidardeleteLiberar recursos, provocando fugas de recursos.
  2. existirdeleteEl programa salió normalmente antes (por ejemplo, siifmedioreturn) o salir de forma anormal antes de que sea demasiado tardedelete, lo que lleva a la fuga de recursos
  3. El mismo recurso se publica varias veces, lo que provoca que se publiquen punteros salvajes y que el programa falle.

En este momento, se necesitan consejos inteligentes. La palabra inteligencia de los punteros inteligentes se refleja principalmente en el hecho de que los usuarios no necesitan prestar atención a la liberación de recursos, porque los punteros inteligentes lo ayudarán a administrar completamente la liberación de recursos, lo que garantizará que no importa cómo se ejecute la lógica del programa. , se ejecutará normalmente o generará excepciones.Cuando los recursos caduquen (alcance de la función o fin del programa), se liberarán.

Implementemos nosotros mismos un puntero inteligente simple

template<typename T>
class CSmartPtr
{
public:
	CSmartPtr(T* ptr = nullptr) : mptr(ptr) {}
	~CSmartPtr() { delete mptr; }
private:
	T* mptr;
};

int main()
{
	CSmartPtr<int> ptr(new int());
	/*其它的代码...*/

	/*
	由于ptr是栈上的智能指针对象,不管是函数正常执行完,
	还是运行过程中出现异常,栈上的对象都会自动调用析构函数,
	在析构函数中进行了delete操作,保证释放资源
	*/

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  1. Los punteros inteligentes se incorporan en una encapsulación orientada a objetos de punteros sin formato, inicializando direcciones de recursos en el constructor y liberando recursos en el destructor.
  2. Al utilizar la función de destrucción automática del alcance de los objetos en la pila, se garantiza que los recursos se liberarán en el destructor del puntero inteligente.

Por lo tanto, debido a las características anteriores, los punteros inteligentes generalmente se definen en la pila. al mismo tiempo,Un puntero inteligente es un objeto de clase. , se pasa un puntero en el constructor de esta clase y el puntero pasado se libera en el destructor.Dado que este tipo de objeto se asigna y libera en la pila, se liberará automáticamente cuando finalice nuestra función (o programa).

Entonces, ¿se pueden definir punteros inteligentes en el montón?Por ejemploCSmartPtr* p = new CSmartPtr(new int);, La compilación puede pasar, pero la definición aquí.pAunque es un tipo de puntero inteligente, es esencialmente un puntero sin formato, por lo quepTodavía necesito hacerlo manualmente.delete, volvemos al problema que enfrentamos con los punteros simples al principio, así que no lo uses así

Por supuesto, los punteros inteligentes deben ser similares a los punteros sin formato y también deben proporcionar características comunes de los punteros sin formato.*y->Las funciones sobrecargadas de los dos operadores son realmente las mismas que los punteros sin formato cuando se usan. El código es el siguiente:

template<typename T>
class CSmartPtr
{
public:
	CSmartPtr(T* ptr = nullptr) : mptr(ptr) {}
	~CSmartPtr() { delete mptr; }

	T& operator*() { return *mptr; }
	T* operator->() { return mptr; }

private:
	T* mptr;
};


int main()
{
	CSmartPtr<int> ptr(new int());

	*ptr = 20;	// operator*()一定要返回引用,这样才可以赋值

	cout << *ptr << endl;	// 20

	class Test
	{
	public:
		void test() { cout << "call Test::test()" << endl; }
	};

	CSmartPtr<Test> ptr2 = new Test();

	(*ptr2).test();	// (*ptr2)取出Test对象,用对象调用方法

	// operator->()返回的是一个指针,实现了用指针调用函数
	// 即(ptr2.operator->()) -> test()
	ptr2->test();

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

2. Punteros inteligentes sin recuento de referencias

El puntero inteligente implementado en la sección anterior es muy similar a un puntero sin formato ordinario, pero todavía tiene grandes problemas. Consulte el siguiente código:

CSmartPtr<int> p1(new int());
CSmartPtr<int> p2(p1);	// 拷贝构造
  • 1
  • 2

El código en ejecución falla directamente porque el constructor de copia predeterminado realiza una copia superficial.p1yp2sostiene lo mismonew intrecurso,p2La destrucción primero libera los recursos, luegop1Cuando se destruye, se conviertedeletePuntero salvaje, el programa falla

Entonces, ¿cómo solucionar los problemas causados ​​por la copia superficial?volver a escribirCSmartPtrEche un vistazo al constructor de copias.

CSmartPtr(const CSmartPtr<T>& src) { mptr = new T(*src.mptr); }
  • 1

Ahora el código se ejecuta normalmente, sin embargo, en este puntop1yp2Se gestionan dos recursos diferentes, si los usuarios no los entienden, pensarán erróneamente quep1yp2Gestionar el mismo recurso

Por lo tanto, es incorrecto que escribamos el constructor de copia de esta manera.

Entonces, ¿cómo resolver el problema de la copia superficial de los punteros inteligentes? Dos métodos:Punteros inteligentes sin recuento de referencias, punteros inteligentes con recuento de referencias

Veamos esta sección primero.Punteros inteligentes sin recuento de referencias

  1. auto_ptr(C++98, ahora en desuso)
  2. scoped_ptr(Impulsar biblioteca)
  3. unique_ptr(C++11, recomendado)

Incluya el archivo de encabezado cuando utilice:#include <memory>

PTR automático

Consideremos este código primero.

auto_ptr<int> ptr1(new int());
auto_ptr<int> ptr2(ptr1);

*ptr2 = 20;
cout << *ptr1 << endl;
  • 1
  • 2
  • 3
  • 4
  • 5

La operación fracasó. ¿Por qué?auto_ptrcódigo fuente

Insertar descripción de la imagen aquí

Puedes ver que la construcción de copia llamará al objeto entrante.releasemétodo, este método es poner_RightEl recurso señalado se devuelve al nuevoauto_ptrobjeto y al mismo tiempo_Right(viejoauto_ptr)de_Myptrajustado anullptr

En definitiva, es poner lo viejoauto_ptrEl recurso señalado se entrega al nuevoauto_ptrRetenido, el antiguo está configurado paranullptr .Por lo tanto, el código anterior utiliza*ptr1Simplemente incorrecto.auto_ptrDeje siempre que el último puntero inteligente administre el recurso)

Entonces,auto_ptr¿Se puede utilizar en un contenedor? Consulte el siguiente código.

int main()
{
	vector<auto_ptr<int>> vec;
	vec.push_back(auto_ptr<int>(new int(10)));
	vec.push_back(auto_ptr<int>(new int(20)));
	vec.push_back(auto_ptr<int>(new int(30)));
	
	cout << *vec[0] << endl;	// 10
	
	vector<auto_ptr<int>> vec2 = vec;
	/* 这里由于上面做了vector容器的拷贝,
	相当于容器中的每一个元素都进行了拷贝构造,
	原来vec中的智能指针全部为nullptr了,
	再次访问就成访问空指针了,程序崩溃
	*/
	
	cout << *vec[0] << endl;	// 程序崩溃
	
	return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

Por lo tanto, está en desuso en C++.auto_ptr , a menos que el escenario de aplicación sea muy simple.yauto_ptrEn desuso en C++ 11 y eliminado por completo en C++ 17

Resumir:auto_ptrLos punteros inteligentes no cuentan con recuento de referencias, por lo que solucionan el problema de la copia superficial copiando directamente el anterior.auto_ptrestán configurados paranullptr, deja solo el últimoauto_ptrretener recursos

punto_de_ámbito

Debe instalar la biblioteca Boost, que incluye los archivos de encabezado.#include <boost/scoped_ptr.hpp>Listo para usar

echar un vistazoscoped_ptrCódigo fuente:

template<class T> class scoped_ptr
{
private:
	scoped_ptr(scoped_ptr const&);
	scoped_ptr& operator=(scoped_ptr const&);
...
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

puede ser vistoscoped_ptrTanto el constructor de copias como la función de asignación están privatizados, por lo que el objeto no admite estas dos operaciones y no puede ser llamado, lo que fundamentalmente evita la aparición de copias superficiales.

entoncesscoped_ptrNo se puede utilizar en contenedores. Si los contenedores se copian o asignan valores entre sí, provocará.scoped_ptrConstructor de copia de objetos y función de asignación, error de compilación

scoped_ptryauto_ptrLa diferencia: Puede explicarse por la propiedad,auto_ptrLa propiedad de los recursos puede transferirse a voluntad, mientras quescoped_ptrLa propiedad no se transfiere (porque los constructores de copia y las funciones de asignación están deshabilitados)

scoped_ptrGeneralmente no se usa

PTR único

Echa un vistazo primerounique_ptrParte del código fuente:

template<class _Ty, class _Dx>
class unique_ptr
{
public:
	/*提供了右值引用的拷贝构造函数*/
	unique_ptr(unique_ptr&& _Right) { ... }
	
	/*提供了右值引用的operator=赋值重载函数*/
	unique_ptr& operator=(unique_ptr&& _Right) { ... }

	/*
	删除了unique_ptr的拷贝构造和赋值函数,
	因此不能做unique_ptr智能指针对象的拷贝构造和赋值,
	防止浅拷贝的发生
	*/
	unique_ptr(const unique_ptr&) = delete;
	unique_ptr& operator=(const unique_ptr&) = delete;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

Visto desde arriba,unique_ptrun pocoscoped_ptrSe hace lo mismo, es decir, las funciones sobrecargadas de construcción y asignación de copias están deshabilitadas y los usuarios tienen prohibido usarunique_ptrRealice la construcción y asignación de copias explícitas para evitar la aparición de problemas de copia superficial del puntero inteligente.

Como no hay un constructor de copias,unique_ptr<int> p1(new int()); unique_ptr<int> p2(p1);Naturalmente esta mal

perounique_ptrProporciona constructores de copia y funciones de asignación con parámetros de referencia rvalue, es decir,unique_ptrLos punteros inteligentes pueden realizar operaciones de construcción y asignación de copias a través de referencias de valores o generarunique_ptrUn lugar para objetos temporales, p.e.unique_ptrComo valor de retorno de la función, el código de muestra es el siguiente:

// 示例1
unique_ptr<int> p1(new int());
unique_ptr<int> p2(move(p1));	// 使用了右值引用的拷贝构造
p2 = move(p1);	// 使用了右值引用的operator=赋值重载函数
  • 1
  • 2
  • 3
  • 4
// 示例2
unique_ptr<int> test_uniqueptr()
{
	unique_ptr<int> ptr(new int());
	return ptr;
}
int main()
{
	/*
	此处调用test_uniqueptr函数,在return ptr代码处,
	调用右值引用的拷贝构造和赋值函数
	*/
	unique_ptr<int> p = test_uniqueptr();	// 调用带右值引用的拷贝构造函数
	p = test_uniqueptr();	// 调用带右值引用的operator=赋值重载函数
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

Entonces usaunique_ptrEl beneficio es que los usuarios pueden usar algo comounique_ptr<int> p2(move(p1));declaración, se puede ver claramente quep1Recursos transferidos ap2p1No se retienen más recursos si los usa.auto_ptrNo será explícitamentemoveEscrito, la intención no es obvia. Si no comprende la capa subyacente, la usará incorrectamente.

Al mismo tiempo, desdeunique_ptrComo puede verse por el nombre, al final solo puede haber un puntero inteligente que haga referencia a un recurso. Por lo tanto, se recomienda dar prioridad al uso de punteros inteligentes sin recuento de referencias.unique_ptr

3. Punteros inteligentes con recuento de referencias

Los punteros inteligentes con recuento de referencias incluyen principalmenteshared_ptryweak_ptr, con recuento de referenciasbeneficioEs decir, varios punteros inteligentes pueden gestionar el mismo recurso. Entonces, ¿qué son los punteros inteligentes con recuento de referencias?

Con recuento de referencias: haga coincidir un recuento de referencia para el recurso de cada objeto.

Cuando se permite que varios punteros inteligentes apunten al mismo recurso, cada puntero inteligente agregará 1 al recuento de referencias del recurso. Cuando se destruye un puntero inteligente, también disminuirá el recuento de referencias del recurso en 1, de modo que el El último puntero inteligente Cuando el recuento de referencias de un recurso disminuye de 1 a 0, significa que el recurso se puede liberar. El destructor del último puntero inteligente maneja la liberación del recurso.

  • Cuando el recuento de referenciasmenos 1 no es 0Cuando, el puntero inteligente actual ya no usa este recurso, pero hay otros punteros inteligentes que usan este recurso. El puntero inteligente actual no puede destruir este recurso y solo puede ir directamente.
  • Cuando el recuento de referenciasDisminuir 1 a 0Cuando, significa que el puntero inteligente actual es el último puntero inteligente que utiliza este recurso, por lo que es responsable de la liberación de este recurso.

Simular la implementación de un puntero inteligente con recuento de referencias.

Tome lo que se implementó anteriormenteCSmartPtrRealice modificaciones y cargue el código directamente:

// 对资源进行引用计数的类
template<typename T>
class RefCnt
{
public:
	RefCnt(T* ptr = nullptr) : mptr(ptr)
	{
		if (mptr != nullptr)
			mcount = 1;
	}
	void addRef() { mcount++; }	// 增加资源的引用计数
	int delRef() { return --mcount; }

private:
	T* mptr;
	int mcount;
};

template<typename T>
class CSmartPtr
{
public:
	CSmartPtr(T* ptr = nullptr) : mptr(ptr)
	{
		// 智能指针构造的时候给资源建立引用计数对象
		mpRefCnt = new RefCnt<T>(mptr);
	}
	~CSmartPtr()
	{
		if (0 == mpRefCnt->delRef())
		{
			delete mptr;
			mptr = nullptr;
		}
	}

	// 实现拷贝构造
	CSmartPtr(const CSmartPtr<T>& src)
		:mptr(src.mptr), mpRefCnt(src.mpRefCnt)
	{
		if (mptr != nullptr)
			mpRefCnt->addRef();
	}
	CSmartPtr<T>& operator=(const CSmartPtr<T>& src)
	{
		// 防止自赋值
		if (this == &src)
			return *this;

		// 本身指向的资源减1
		// 如果减1为0释放资源;如果减1不为0直接走
		if (0 == mpRefCnt->delRef()) { delete mptr; }

		mptr = src.mptr;
		mpRefCnt = src.mpRefCnt;
		mpRefCnt->addRef();
		return *this;
	}

	T& operator*() { return *mptr; }
	T* operator->() { return mptr; }

private:
	T* mptr;	// 指向资源的指针
	RefCnt<T>* mpRefCnt;	// 指向该资源引用计数对象的指针
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
// 那么现在就不会报错了,不会对同一个资源释放多次
CSmartPtr<int> ptr1(new int());
CSmartPtr<int> ptr2(ptr1);
CSmartPtr<int> ptr3;
ptr3 = ptr2;

*ptr1 = 20;
cout << *ptr2 << " " << *ptr3 << endl;	// 20 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Esto permite que varios punteros inteligentes administren el mismo recurso.

Sin embargo, los punteros inteligentes que implementamos ahora no son seguros para subprocesos y no se pueden utilizar en escenarios de subprocesos múltiples.Implementado por Curryshared_ptryweak_ptr¡Es seguro para subprocesos!

Problema de referencia cruzada de Shared_ptr

  • shared_ptrpoderosoPunteros inteligentes (puede cambiar el recuento de referencia de recursos)
  • weak_ptrdébilPunteros inteligentes (no cambie el recuento de referencias del recurso)

Lo que implementamos en la sección anteriorCSmartPtrTambién un potente puntero inteligente (puede cambiar el recuento de referencias del recurso)

Se puede entender de esta manera: Puntero inteligente débil observar Fuertes consejos inteligentes Fuertes consejos inteligentesobservar Recursos (memoria)

Entonces,Fuerte problema de referencia cruzada de puntero inteligente (referencia circular) ¿entonces que es?Ven y echa un vistazo

class B; // 前置声明类B
class A
{
public:
	A() { cout << "A()" << endl; }
	~A() { cout << "~A()" << endl; }
	shared_ptr<B> _ptrb;	// 指向B对象的智能指针
};
class B
{
public:
	B() { cout << "B()" << endl; }
	~B() { cout << "~B()" << endl; }
	shared_ptr<A> _ptra;	// 指向A对象的智能指针
};

int main()
{
	shared_ptr<A> pa(new A());	// pa指向A对象,A的引用计数为1
	shared_ptr<B> pb(new B());	// pb指向B对象,B的引用计数为1
	pa->_ptrb = pb;	// A对象的成员变量_ptrb也指向B对象,B的引用计数为2
	pb->_ptra = pa;	// B对象的成员变量_ptra也指向A对象,A的引用计数为2

	cout << pa.use_count() << endl;
	cout << pb.use_count() << endl;

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

resultado de la operación:

A()
B()
2
2
  • 1
  • 2
  • 3
  • 4

Como puede ver, A y B no se destruyen, lo que significa que las referencias cruzadas causaránnewLos recursos que salen no se pueden liberar, lo que provoca una fuga de recursos.

analizar:
Insertar descripción de la imagen aquíInsertar descripción de la imagen aquí
afueramainalcance de la función,paypbSe destruyen dos objetos locales y los recuentos de referencia del objeto A y el objeto B se reducen de 2 a 1 respectivamente. No se pueden cumplir las condiciones para liberar A y B (la condición para la liberación es que los recuentos de referencia de A y B se reduzcan). a 0), provocando así dosnewLos objetos A y B que salieron no se pueden liberar, lo que provoca pérdidas de memoria.Fuerte problema de referencia cruzada de puntero inteligente (referencia circular)

Solución: utilice punteros inteligentes fuertes al definir objetos y utilice punteros inteligentes débiles al hacer referencia a objetos.

class B; // 前置声明类B
class A
{
public:
	A() { cout << "A()" << endl; }
	~A() { cout << "~A()" << endl; }
	weak_ptr<B> _ptrb;	// 指向B对象的弱智能指针(引用对象时,用弱智能指针)
};
class B
{
public:
	B() { cout << "B()" << endl; }
	~B() { cout << "~B()" << endl; }
	weak_ptr<A> _ptra;	// 指向A对象的弱智能指针(引用对象时,用弱智能指针)
};


int main()
{
	// 定义对象时,用强智能指针
	shared_ptr<A> pa(new A());	// pa指向A对象,A的引用计数为1
	shared_ptr<B> pb(new B());	// pb指向B对象,B的引用计数为1

	// A对象的成员变量_ptrb也指向B对象,B的引用计数为1,因为是弱智能指针,引用计数没有改变
	pa->_ptrb = pb;
	// B对象的成员变量_ptra也指向A对象,A的引用计数为1,因为是弱智能指针,引用计数没有改变
	pb->_ptra = pa;

	cout << pa.use_count() << endl; // 1
	cout << pb.use_count() << endl; // 1

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

resultado de la operación:

A()
B()
1
1
~B()
~A()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

se puede ver, afueramainalcance de la función,paypbLos dos objetos locales se destruyen y los recuentos de referencia del objeto A y del objeto B respectivamente se reducen de 1 a 0, alcanzando las condiciones para liberar A y B. Por lo tanto,newEl objeto A y el objeto B que salieron fueron destruidos, lo que resolvió el problema.Fuerte problema de referencia cruzada de puntero inteligente (referencia circular)

Insertar descripción de la imagen aquí
puede ser visto,weak_ptrLos punteros inteligentes débiles no cambiarán el recuento de referencias del recurso, lo que significa que el puntero inteligente débil solo observa si el objeto está vivo (si el recuento de referencias es 0) y no puede utilizar el recurso.

Entonces, si en este momento, agregue la clase A.void testA() { cout << "非常好用的方法!!" << endl; }Este método se suma a Bvoid func() { _ptra->testA(); }¿Está bien llamar a este método?

  • No poder, porque el puntero inteligente débil es solo un observador y no puede usar recursos, es decir, no proporciona*y->Las funciones sobrecargadas de los operadores no pueden utilizar funciones similares a los punteros sin formato

Solución:

class B; // 前置声明类B
class A
{
public:
	A() { cout << "A()" << endl; }
	~A() { cout << "~A()" << endl; }
	void testA() { cout << "非常好用的方法!!" << endl; }
	weak_ptr<B> _ptrb;	// 指向B对象的弱智能指针(引用对象时,用弱智能指针)
};
class B
{
public:
	B() { cout << "B()" << endl; }
	~B() { cout << "~B()" << endl; }
	void func()
	{
		shared_ptr<A> sp = _ptra.lock(); // 提升方法,出函数作用域就自动析构了
		if (sp != nullptr)
			sp->testA();
	}
	weak_ptr<A> _ptra;	// 指向A对象的弱智能指针(引用对象时,用弱智能指针)
};

int main()
{
	shared_ptr<A> pa(new A());
	shared_ptr<B> pb(new B());
	pa->_ptrb = pb;
	pb->_ptra = pa;
	cout << pa.use_count() << endl; // 1
	cout << pb.use_count() << endl; // 1

	pb->func();

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

A pesar de weak_ptrno es dueño del objeto, pero puede pasarlock()El método intenta obtener un puntero al objeto.shared_ptr, si el objeto todavía está vivo (es decir, hay otrosshared_ptrpunto a esto),lock()devolverá un puntero al objetoshared_ptr; De lo contrario, se devolverá un valor vacío.shared_ptr

usarlock()Un escenario típico para los métodos es cuando se requiere acceso temporal.weak_ptrel objeto señalado y, al mismo tiempo, no desea aumentar el recuento de vida útil del objeto

resultado de la operación:

A()
B()
1
1
非常好用的方法!!
~B()
~A()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

¡En este punto puedes ver que se llamó correctamente!

4. Problemas de seguridad de subprocesos cuando varios subprocesos acceden a objetos compartidos

veamos unoProblemas de seguridad de subprocesos al acceder a objetos compartidos desde varios subprocesos: El subproceso A y el subproceso B acceden a un objeto compartido. Si el subproceso A está destruyendo el objeto, el subproceso B necesita llamar al método miembro del objeto compartido. En este momento, es posible que el subproceso A haya terminado de destruir el objeto y el subproceso B no lo hará. Si intenta acceder al objeto, se producirá un error inesperado.

Primero mire el siguiente código:

class A
{
public:
	A() { cout << "A()" << endl; }
	~A() { cout << "~A()" << endl; }
	void testA() { cout << "非常好用的方法!!" << endl; }
};

// 子线程
void handler01(A* q)
{
	// 睡眠两秒,此时main主线程已经把A对象给delete析构掉了
	std::this_thread::sleep_for(std::chrono::seconds(2));
	q->testA();
}

// main线程
int main()
{
	A* p = new A();
	thread t1(handler01, p);
	delete p;
	//阻塞当前线程,直到调用join()的线程结束
	t1.join();
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

en ejecuciónq->testA();Cuando se hace esta declaración, elmainEl hilo ha destruido el objeto compartido, lo que obviamente no es razonable.

si quieres pasarqSi el puntero quiere acceder al objeto A, necesita determinar si el objeto A está vivo. Si el objeto A está vivo, llame.testANo hay problema con el método si el objeto A ha sido destruido, llámelo;testA ¡tiene un problema!Es decirqAl acceder al objeto A, debe detectar si el objeto A está vivo. ¿Cómo resolver este problema? ¿Necesita utilizar punteros inteligentes fuertes y débiles?

Veamos este código:

// 子线程
void handler01(weak_ptr<A> wp)
{
	// 睡眠两秒
	std::this_thread::sleep_for(std::chrono::seconds(2));
	shared_ptr<A> sp = wp.lock();
	if (sp != nullptr)
		sp->testA();
	else
		cout << "A对象已经析构,不能再访问!" << endl;
}

// main线程
int main()
{
	shared_ptr<A> p(new A());
	thread t1(handler01, weak_ptr<A>(p));
	t1.join();
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

resultado de la operación:

A()
非常好用的方法!!
~A()
  • 1
  • 2
  • 3

Puedes ver que está funcionando.sp->testA();,porquemainEl hilo llamadot1.join()El método espera a que finalice el subproceso secundario.wpaprobarlockPromovido exitosamente asp

Modifique el código anterior:

// 子线程
void handler01(weak_ptr<A> wp)
{
	// 睡眠两秒
	std::this_thread::sleep_for(std::chrono::seconds(2));
	shared_ptr<A> sp = wp.lock();
	if (sp != nullptr)
		sp->testA();
	else
		cout << "A对象已经析构,不能再访问!" << endl;
}

// main线程
int main()
{
	{
		shared_ptr<A> p(new A());
		thread t1(handler01, weak_ptr<A>(p));
		t1.detach();
	}
	std::this_thread::sleep_for(std::chrono::seconds(3));
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

resultado de la operación:

A()
~A()
A对象已经析构,不能再访问!
  • 1
  • 2
  • 3

Como puede ver, establecemos un alcance y también configuramost1Para separar hilos, deje que los punteros inteligentespCuando A se destruye fuera del alcance, se imprimirá en este momento.A对象已经析构,不能再访问!, eso eswpaprobarlockNo se pudo actualizar exitosamente asp

Lo anterior es el problema de seguridad de subprocesos cuando varios subprocesos acceden a objetos compartidos. Esta es una referencia a.shared_ptryweak_ptrUna aplicación típica.

5. Eliminador de puntero inteligente

6. Se recomienda utilizar make_shared en lugar deshared_ptr


Artículo de referencia:Comprensión profunda de los punteros inteligentes de C++