minhas informações de contato
Correspondência[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Desvantagens dos ponteiros brutos:
delete
Liberar recursos, causando vazamento de recursosdelete
O programa saiu normalmente antes (por exemplo, seif
meioreturn
) ou saia de forma anormal antes que seja tarde demaisdelete
, levando ao vazamento de recursosNeste 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;
}
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 aquip
Embora seja um tipo de ponteiro inteligente, é essencialmente um ponteiro bruto, entãop
Ainda 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;
}
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); // 拷贝构造
O código em execução falha diretamente porque o construtor de cópia padrão faz uma cópia superficial.p1
ep2
mantém o mesmonew int
recurso,p2
A destruição primeiro libera os recursos, depoisp1
Quando destruído, torna-sedelete
Ponteiro selvagem, falha do programa
Então, como resolver os problemas causados pela cópia superficial?reescreverCSmartPtr
Dê uma olhada no construtor de cópia
CSmartPtr(const CSmartPtr<T>& src) { mptr = new T(*src.mptr); }
Agora o código funciona normalmente, porém, neste pontop1
ep2
Dois recursos diferentes são gerenciados. Se os usuários não os compreenderem, eles pensarão erroneamente que isso acontece.p1
ep2
Gerencie 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
auto_ptr
(C++98, agora obsoleto)scoped_ptr
(Aumentar biblioteca)unique_ptr
(C++11, recomendado)Inclua o arquivo de cabeçalho ao usar:#include <memory>
Vamos considerar este código primeiro
auto_ptr<int> ptr1(new int());
auto_ptr<int> ptr2(ptr1);
*ptr2 = 20;
cout << *ptr1 << endl;
A operação travou. Por quê?auto_ptr
Código fonte
Você pode ver que a construção da cópia chamará o objeto recebidorelease
método, este método é colocar_Right
O recurso apontado é retornado para o novoauto_ptr
objeto e ao mesmo tempo_Right
(velhoauto_ptr
)de_Myptr
definido comonullptr
Em suma, é colocar o velhoauto_ptr
O recurso apontado é dado ao novoauto_ptr
Realizado, o antigo está definido paranullptr
.Portanto, o código acima usa*ptr1
Simplesmente errado.(auto_ptr
Sempre deixe o último ponteiro inteligente gerenciar o recurso)
Então,auto_ptr
Ele 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;
}
Portanto, está obsoleto em C++auto_ptr
, a menos que o cenário de aplicação seja muito simples.eauto_ptr
Obsoleto em C++ 11 e totalmente removido em C++ 17
Resumir:auto_ptr
Os ponteiros inteligentes não possuem contagem de referências, portanto, eles lidam com o problema da cópia superficial copiando diretamente o anterior.auto_ptr
estão definidos paranullptr
, deixe apenas o últimoauto_ptr
reter recursos
Você precisa instalar a biblioteca Boost, que inclui os arquivos de cabeçalho.#include <boost/scoped_ptr.hpp>
Pronto para usar
dê uma olhadascoped_ptr
Código fonte:
template<class T> class scoped_ptr
{
private:
scoped_ptr(scoped_ptr const&);
scoped_ptr& operator=(scoped_ptr const&);
...
};
pode ser vistoscoped_ptr
O 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_ptr
Não pode ser usado em contêineres. Se os contêineres copiarem ou atribuirem valores uns aos outros, isso causará.scoped_ptr
Construtor de cópia de objeto e função de atribuição, erro de compilação
scoped_ptr
eauto_ptr
A diferença: Pode ser explicado pela propriedade,auto_ptr
A propriedade dos recursos pode ser transferida à vontade, enquantoscoped_ptr
A propriedade não é transferida (porque os construtores de cópia e as funções de atribuição estão desabilitados)
scoped_ptr
Geralmente não usado
Dê uma olhada primeirounique_ptr
Parte 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;
};
Visto de cima,unique_ptr
um poucoscoped_ptr
O 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_ptr
Execute 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_ptr
Fornece construtores de cópia e funções de atribuição com parâmetros de referência de valor, ou seja,unique_ptr
Ponteiros inteligentes podem realizar operações de construção de cópia e atribuição por meio de referências de valor ou gerarunique_ptr
Um local para objetos temporários, por ex.unique_ptr
Como 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=赋值重载函数
// 示例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;
}
Então useunique_ptr
A vantagem é que os usuários podem usar algo comounique_ptr<int> p2(move(p1));
declaração, pode-se ver claramente quep1
Recursos transferidos parap2
,p1
Não há mais recursos retidos se você usar.auto_ptr
Não será explicitamentemove
Escrito, a intenção não é óbvia. Se você não entender a camada subjacente, você a usará incorretamente.
Ao mesmo tempo, deunique_ptr
Como 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
Ponteiros inteligentes com contagem de referência incluem principalmenteshared_ptr
eweak_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.
Pegue o que foi implementado anteriormenteCSmartPtr
Faç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; // 指向该资源引用计数对象的指针
};
// 那么现在就不会报错了,不会对同一个资源释放多次
CSmartPtr<int> ptr1(new int());
CSmartPtr<int> ptr2(ptr1);
CSmartPtr<int> ptr3;
ptr3 = ptr2;
*ptr1 = 20;
cout << *ptr2 << " " << *ptr3 << endl; // 20 20
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_ptr
eweak_ptr
É thread-safe!
shared_ptr
:poderosoPonteiros inteligentes (podem alterar a contagem de referência de recursos)weak_ptr
:fracoPonteiros inteligentes (não alteram a contagem de referência do recurso)O que implementamos na seção anteriorCSmartPtr
També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;
}
resultado da operação:
A()
B()
2
2
Como você pode ver, A e B não são destruídos, o que significa que a referência cruzada causaránew
Os recursos que saem não podem ser liberados, resultando em vazamento de recursos!
analisar:
foramain
escopo de função,pa
epb
Dois 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 doisnew
Os 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;
}
resultado da operação:
A()
B()
1
1
~B()
~A()
pode ser visto, foramain
escopo de função,pa
epb
Os 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,new
O 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)
pode ser visto,weak_ptr
Ponteiros 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?
*
e->
Funções sobrecarregadas de operadores não podem usar funções semelhantes a ponteiros brutosSoluçã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;
}
Embora
weak_ptr
nã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_ptr
apontar para isso),lock()
retornará um ponteiro para o objetoshared_ptr
; Caso contrário, um valor vazio será retornado.shared_ptr
usar
lock()
Um cenário típico para métodos é quando o acesso temporário é necessárioweak_ptr
o 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()
Neste ponto você pode ver que foi chamado corretamente!
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;
}
em execuçãoq->testA();
Quando esta afirmação é feita, omain
O objeto compartilhado foi destruído pelo thread, o que obviamente não é razoável.
Se você quiser passarq
Se o ponteiro quiser acessar o objeto A, ele precisará determinar se o objeto A está ativo. Se o objeto A estiver ativo, chame.testA
Não há problema com o método; se o objeto A foi destruído, chame;testA
tem um problema!Isso quer dizerq
Ao 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;
}
resultado da operação:
A()
非常好用的方法!!
~A()
Você pode ver que está funcionandosp->testA();
,porquemain
O tópico chamadot1.join()
O método espera o thread filho terminar neste momento.wp
passarlock
Promovido 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;
}
resultado da operação:
A()
~A()
A对象已经析构,不能再访问!
Como você pode ver, definimos um escopo e também definimost1
Para separar threads, deixe ponteiros inteligentesp
Quando A for destruído fora do escopo, ele será impresso neste momentoA对象已经析构,不能再访问!
, aquilo éwp
passarlock
Falha 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_ptr
eweak_ptr
Uma aplicação típica.
Artigo de referência:Compreensão profunda dos ponteiros inteligentes C++