Compartilhamento de tecnologia

Experimente o poder dos ponteiros inteligentes

2024-07-12

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


Catálogo de cursos



1. Conhecimento básico de ponteiros inteligentes

Desvantagens dos ponteiros brutos:

  1. esquecerdeleteLiberar recursos, causando vazamento de recursos
  2. existirdeleteO programa saiu normalmente antes (por exemplo, seifmeioreturn) ou saia de forma anormal antes que seja tarde demaisdelete, levando ao vazamento de recursos
  3. O mesmo recurso é liberado várias vezes, fazendo com que ponteiros selvagens sejam liberados e o programa trave.

Neste momento, são necessários ponteiros inteligentes. A palavra inteligência dos ponteiros inteligentes se reflete principalmente no fato de que os usuários não precisam se preocupar com a liberação de recursos, pois os ponteiros inteligentes irão ajudá-lo a gerenciar completamente a liberação de recursos. , ele será executado normalmente ou gerará exceções.Quando os recursos expirarem (escopo da função ou final do programa), eles serão liberados.

Vamos implementar nós mesmos um ponteiro inteligente simples

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. Ponteiros inteligentes são incorporados no encapsulamento orientado a objetos de ponteiros brutos, inicializando endereços de recursos no construtor e liberando recursos no destruidor.
  2. Utilizando o recurso de destruição automática de escopo de objetos na pilha, é garantido que os recursos sejam liberados no destruidor do ponteiro inteligente.

Portanto, devido às características acima, os ponteiros inteligentes são geralmente definidos na pilha. ao mesmo tempo,Um ponteiro inteligente é um objeto de classe , um ponteiro é passado no construtor desta classe e o ponteiro passado é liberado no destruidor.Como esse tipo de objeto é alocado e liberado na pilha, ele será liberado automaticamente quando nossa função (ou programa) terminar.

Então, os ponteiros inteligentes podem ser definidos no heap?por exemploCSmartPtr* p = new CSmartPtr(new int);, a compilação pode passar, mas a definição aquipEmbora seja um tipo de ponteiro inteligente, é essencialmente um ponteiro bruto, entãopAinda preciso fazer isso manualmentedelete, estamos de volta ao problema que enfrentamos com dicas básicas no início, então não use assim

É claro que os ponteiros inteligentes devem ser semelhantes aos ponteiros brutos e também devem fornecer recursos comuns aos ponteiros brutos.*e->As funções sobrecarregadas dos dois operadores são realmente iguais aos ponteiros brutos quando usados. O código é o seguinte:

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. Ponteiros inteligentes sem contagem de referência

O ponteiro inteligente implementado na seção anterior é muito semelhante a um ponteiro bruto comum em uso, mas ainda apresenta grandes problemas. Veja o código a seguir:

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

O código em execução falha diretamente porque o construtor de cópia padrão faz uma cópia superficial.p1ep2mantém o mesmonew intrecurso,p2A destruição primeiro libera os recursos, depoisp1Quando destruído, torna-sedeletePonteiro selvagem, falha do programa

Então, como resolver os problemas causados ​​pela cópia superficial?reescreverCSmartPtrDê uma olhada no construtor de cópia

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

Agora o código funciona normalmente, porém, neste pontop1ep2Dois recursos diferentes são gerenciados. Se os usuários não os compreenderem, eles pensarão erroneamente que isso acontece.p1ep2Gerencie o mesmo recurso

Portanto, é errado escrevermos o construtor de cópia assim

Então, como resolver o problema da cópia superficial dos ponteiros inteligentes? Dois métodos:Ponteiros inteligentes sem contagem de referência, ponteiros inteligentes com contagem de referência

Vejamos esta seção primeiroPonteiros inteligentes sem contagem de referência

  1. auto_ptr(C++98, agora obsoleto)
  2. scoped_ptr(Aumentar biblioteca)
  3. unique_ptr(C++11, recomendado)

Inclua o arquivo de cabeçalho ao usar:#include <memory>

auto_ptr

Vamos considerar este código primeiro

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

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

A operação travou. Por quê?auto_ptrCódigo fonte

Insira a descrição da imagem aqui

Você pode ver que a construção da cópia chamará o objeto recebidoreleasemétodo, este método é colocar_RightO recurso apontado é retornado para o novoauto_ptrobjeto e ao mesmo tempo_Right(velhoauto_ptr)de_Myptrdefinido comonullptr

Em suma, é colocar o velhoauto_ptrO recurso apontado é dado ao novoauto_ptrRealizado, o antigo está definido paranullptr .Portanto, o código acima usa*ptr1Simplesmente errado.auto_ptrSempre deixe o último ponteiro inteligente gerenciar o recurso)

Então,auto_ptrEle pode ser usado em um contêiner. Veja o código a seguir.

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

Portanto, está obsoleto em C++auto_ptr , a menos que o cenário de aplicação seja muito simples.eauto_ptrObsoleto em C++ 11 e totalmente removido em C++ 17

Resumir:auto_ptrOs ponteiros inteligentes não possuem contagem de referências, portanto, eles lidam com o problema da cópia superficial copiando diretamente o anterior.auto_ptrestão definidos paranullptr, deixe apenas o últimoauto_ptrreter recursos

escopo_ptr

Você precisa instalar a biblioteca Boost, que inclui os arquivos de cabeçalho.#include <boost/scoped_ptr.hpp>Pronto para usar

dê uma olhadascoped_ptrCódigo fonte:

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

pode ser vistoscoped_ptrO construtor de cópia e a função de atribuição são ambos privatizados, de modo que o objeto não suporta essas duas operações e não pode ser chamado, o que evita fundamentalmente a ocorrência de cópias superficiais.

entãoscoped_ptrNão pode ser usado em contêineres. Se os contêineres copiarem ou atribuirem valores uns aos outros, isso causará.scoped_ptrConstrutor de cópia de objeto e função de atribuição, erro de compilação

scoped_ptreauto_ptrA diferença: Pode ser explicado pela propriedade,auto_ptrA propriedade dos recursos pode ser transferida à vontade, enquantoscoped_ptrA propriedade não é transferida (porque os construtores de cópia e as funções de atribuição estão desabilitados)

scoped_ptrGeralmente não usado

ptr_único

Dê uma olhada primeirounique_ptrParte do código-fonte:

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 de cima,unique_ptrum poucoscoped_ptrO mesmo é feito, ou seja, as funções sobrecarregadas de construção e atribuição de cópias são desabilitadas e os usuários são proibidos de usarunique_ptrExecute a construção e atribuição explícita de cópias para evitar a ocorrência de problemas de cópia superficial do ponteiro inteligente.

Como não há construtor de cópia,unique_ptr<int> p1(new int()); unique_ptr<int> p2(p1);Naturalmente está errado

masunique_ptrFornece construtores de cópia e funções de atribuição com parâmetros de referência de valor, ou seja,unique_ptrPonteiros inteligentes podem realizar operações de construção de cópia e atribuição por meio de referências de valor ou gerarunique_ptrUm local para objetos temporários, por ex.unique_ptrComo valor de retorno da função, o código de exemplo é o seguinte:

// 示例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

Então useunique_ptrA vantagem é que os usuários podem usar algo comounique_ptr<int> p2(move(p1));declaração, pode-se ver claramente quep1Recursos transferidos parap2p1Não há mais recursos retidos se você usar.auto_ptrNão será explicitamentemoveEscrito, a intenção não é óbvia. Se você não entender a camada subjacente, você a usará incorretamente.

Ao mesmo tempo, deunique_ptrComo pode ser visto pelo nome, no final só pode haver um ponteiro inteligente que se refira a um recurso. Portanto, é recomendável dar prioridade ao uso de ponteiros inteligentes sem contagem de referência.unique_ptr

3. Ponteiros inteligentes com contagem de referência

Ponteiros inteligentes com contagem de referência incluem principalmenteshared_ptreweak_ptr, com contagem de referênciabeneficiarOu seja, vários ponteiros inteligentes podem gerenciar o mesmo recurso. Então, o que são ponteiros inteligentes com contagem de referência?

Com contagem de referência: corresponde a uma contagem de referência para o recurso de cada objeto.

Quando vários ponteiros inteligentes podem apontar para o mesmo recurso, cada ponteiro inteligente adicionará 1 à contagem de referência do recurso. Quando um ponteiro inteligente for destruído, ele também diminuirá a contagem de referência do recurso em 1, de modo que o ponteiro inteligente seja destruído. o último ponteiro inteligente irá Quando a contagem de referência de um recurso diminui de 1 para 0, significa que o recurso pode ser liberado. O destruidor do último ponteiro inteligente lida com a liberação do recurso.

  • Quando a contagem de referênciamenos 1 não é 0Quando, o ponteiro inteligente atual não usa mais esse recurso, mas há outros ponteiros inteligentes que usam esse recurso. O ponteiro inteligente atual não pode destruir esse recurso e só pode ir diretamente.
  • Quando a contagem de referênciaDiminuir 1 para 0Quando, significa que o ponteiro inteligente atual é o último ponteiro inteligente a usar este recurso, portanto é responsável pela liberação deste recurso.

Simule a implementação de um ponteiro inteligente com contagem de referências

Pegue o que foi implementado anteriormenteCSmartPtrFaça modificações e carregue o código diretamente:

// 对资源进行引用计数的类
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

Isso permite que vários ponteiros inteligentes gerenciem o mesmo recurso.

No entanto, os ponteiros inteligentes que implementamos agora não são seguros para threads e não podem ser usados ​​em cenários multithread.Implementado por Curryshared_ptreweak_ptrÉ thread-safe!

problema de referência cruzada shared_ptr

  • shared_ptrpoderosoPonteiros inteligentes (podem alterar a contagem de referência de recursos)
  • weak_ptrfracoPonteiros inteligentes (não alteram a contagem de referência do recurso)

O que implementamos na seção anteriorCSmartPtrTambém um ponteiro inteligente forte (pode alterar a contagem de referência do recurso)

Pode ser entendido desta forma: Ponteiro inteligente fraco observar Ponteiros inteligentes fortes Ponteiros inteligentes fortesobservar Recursos (memória)

Então,Problema forte de referência cruzada de ponteiro inteligente (referência circular) Então o que é?Venha e dê uma olhada

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 da operação:

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

Como você pode ver, A e B não são destruídos, o que significa que a referência cruzada causaránewOs recursos que saem não podem ser liberados, resultando em vazamento de recursos!

analisar:
Insira a descrição da imagem aquiInsira a descrição da imagem aqui
foramainescopo de função,paepbDois objetos locais são destruídos e as contagens de referência do objeto A e do objeto B são reduzidas de 2 para 1, respectivamente. As condições para liberação de A e B não podem ser atendidas (a condição para liberação é que as contagens de referência de A e B sejam reduzidas). para 0), causando assim doisnewOs objetos A e B que saíram não podem ser liberados, causando vazamentos de memória. Esse problema é.Problema forte de referência cruzada de ponteiro inteligente (referência circular)

Solução: Use ponteiros inteligentes fortes ao definir objetos e use ponteiros inteligentes fracos ao fazer referência 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 da operação:

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

pode ser visto, foramainescopo de função,paepbOs dois objetos locais são destruídos, e as contagens de referência do objeto A e do objeto B respectivamente são reduzidas de 1 para 0, atingindo as condições para liberação de A e B. Portanto,newO objeto A e o objeto B que surgiram foram destruídos, o que resolveu o problema.Problema forte de referência cruzada de ponteiro inteligente (referência circular)

Insira a descrição da imagem aqui
pode ser visto,weak_ptrPonteiros inteligentes fracos não alterarão a contagem de referência do recurso, o que significa que o ponteiro inteligente fraco apenas observa se o objeto está ativo (se a contagem de referência é 0) e não pode usar o recurso.

Então se neste momento, adicione na classe Avoid testA() { cout << "非常好用的方法!!" << endl; }Tal método adiciona a Bvoid func() { _ptra->testA(); }Posso chamar esse método?

  • Não pode, porque o ponteiro inteligente fraco é apenas um observador e não pode utilizar recursos, ou seja, não fornece*e->Funções sobrecarregadas de operadores não podem usar funções semelhantes a ponteiros brutos

Solução:

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

Embora weak_ptrnão possui o objeto, mas pode passarlock()método tenta obter um ponteiro para o objetoshared_ptr, se o objeto ainda estiver vivo (ou seja, existem outrosshared_ptrapontar para isso),lock()retornará um ponteiro para o objetoshared_ptr; Caso contrário, um valor vazio será retornado.shared_ptr

usarlock()Um cenário típico para métodos é quando o acesso temporário é necessárioweak_ptro objeto apontado e, ao mesmo tempo, não deseja aumentar a contagem de vida útil do objeto

resultado da operação:

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

Neste ponto você pode ver que foi chamado corretamente!

4. Problemas de segurança de thread quando multi-threads acessam objetos compartilhados

Vamos ver umProblemas de segurança de thread ao acessar objetos compartilhados de vários threads: Thread A e thread B acessam um objeto compartilhado Se o thread A estiver destruindo o objeto, o thread B precisará chamar o método membro do objeto compartilhado. Neste momento, o thread A pode ter terminado de destruir o objeto, e o thread B não o fará. Se você tentar acessar o objeto, ocorrerá um erro inesperado.

Primeiro observe o seguinte 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

em execuçãoq->testA();Quando esta afirmação é feita, omainO objeto compartilhado foi destruído pelo thread, o que obviamente não é razoável.

Se você quiser passarqSe o ponteiro quiser acessar o objeto A, ele precisará determinar se o objeto A está ativo. Se o objeto A estiver ativo, chame.testANão há problema com o método; se o objeto A foi destruído, chame;testA tem um problema!Isso quer dizerqAo acessar o objeto A, você precisa detectar se o objeto A está ativo. Como resolver este problema? Você precisa usar ponteiros inteligentes fortes e fracos.

Vejamos 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 da operação:

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

Você pode ver que está funcionandosp->testA();,porquemainO tópico chamadot1.join()O método espera o thread filho terminar neste momento.wppassarlockPromovido com sucesso parasp

Modifique o código acima:

// 子线程
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 da operação:

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

Como você pode ver, definimos um escopo e também definimost1Para separar threads, deixe ponteiros inteligentespQuando A for destruído fora do escopo, ele será impresso neste momentoA对象已经析构,不能再访问!, aquilo éwppassarlockFalha ao atualizar com sucesso parasp

O acima é o problema de segurança do thread quando multi-threads acessam objetos compartilhados. Esta é uma referência a.shared_ptreweak_ptrUma aplicação típica.

5. Excluidor de ponteiro inteligente

6. Recomenda-se usar make_shared em vez de shared_ptr


Artigo de referência:Compreensão profunda dos ponteiros inteligentes C++