Mi informacion de contacto
Correo[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Desventajas de los punteros sin formato:
delete
Liberar recursos, provocando fugas de recursos.delete
El programa salió normalmente antes (por ejemplo, siif
medioreturn
) o salir de forma anormal antes de que sea demasiado tardedelete
, lo que lleva a la fuga de recursosEn 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;
}
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í.p
Aunque es un tipo de puntero inteligente, es esencialmente un puntero sin formato, por lo quep
Todaví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;
}
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); // 拷贝构造
El código en ejecución falla directamente porque el constructor de copia predeterminado realiza una copia superficial.p1
yp2
sostiene lo mismonew int
recurso,p2
La destrucción primero libera los recursos, luegop1
Cuando se destruye, se conviertedelete
Puntero salvaje, el programa falla
Entonces, ¿cómo solucionar los problemas causados por la copia superficial?volver a escribirCSmartPtr
Eche un vistazo al constructor de copias.
CSmartPtr(const CSmartPtr<T>& src) { mptr = new T(*src.mptr); }
Ahora el código se ejecuta normalmente, sin embargo, en este puntop1
yp2
Se gestionan dos recursos diferentes, si los usuarios no los entienden, pensarán erróneamente quep1
yp2
Gestionar 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
auto_ptr
(C++98, ahora en desuso)scoped_ptr
(Impulsar biblioteca)unique_ptr
(C++11, recomendado)Incluya el archivo de encabezado cuando utilice:#include <memory>
Consideremos este código primero.
auto_ptr<int> ptr1(new int());
auto_ptr<int> ptr2(ptr1);
*ptr2 = 20;
cout << *ptr1 << endl;
La operación fracasó. ¿Por qué?auto_ptr
código fuente
Puedes ver que la construcción de copia llamará al objeto entrante.release
método, este método es poner_Right
El recurso señalado se devuelve al nuevoauto_ptr
objeto y al mismo tiempo_Right
(viejoauto_ptr
)de_Myptr
ajustado anullptr
En definitiva, es poner lo viejoauto_ptr
El recurso señalado se entrega al nuevoauto_ptr
Retenido, el antiguo está configurado paranullptr
.Por lo tanto, el código anterior utiliza*ptr1
Simplemente incorrecto.(auto_ptr
Deje 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;
}
Por lo tanto, está en desuso en C++.auto_ptr
, a menos que el escenario de aplicación sea muy simple.yauto_ptr
En desuso en C++ 11 y eliminado por completo en C++ 17
Resumir:auto_ptr
Los punteros inteligentes no cuentan con recuento de referencias, por lo que solucionan el problema de la copia superficial copiando directamente el anterior.auto_ptr
están configurados paranullptr
, deja solo el últimoauto_ptr
retener recursos
Debe instalar la biblioteca Boost, que incluye los archivos de encabezado.#include <boost/scoped_ptr.hpp>
Listo para usar
echar un vistazoscoped_ptr
Código fuente:
template<class T> class scoped_ptr
{
private:
scoped_ptr(scoped_ptr const&);
scoped_ptr& operator=(scoped_ptr const&);
...
};
puede ser vistoscoped_ptr
Tanto 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_ptr
No se puede utilizar en contenedores. Si los contenedores se copian o asignan valores entre sí, provocará.scoped_ptr
Constructor de copia de objetos y función de asignación, error de compilación
scoped_ptr
yauto_ptr
La diferencia: Puede explicarse por la propiedad,auto_ptr
La propiedad de los recursos puede transferirse a voluntad, mientras quescoped_ptr
La propiedad no se transfiere (porque los constructores de copia y las funciones de asignación están deshabilitados)
scoped_ptr
Generalmente no se usa
Echa un vistazo primerounique_ptr
Parte 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;
};
Visto desde arriba,unique_ptr
un pocoscoped_ptr
Se 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_ptr
Realice 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_ptr
Proporciona constructores de copia y funciones de asignación con parámetros de referencia rvalue, es decir,unique_ptr
Los punteros inteligentes pueden realizar operaciones de construcción y asignación de copias a través de referencias de valores o generarunique_ptr
Un lugar para objetos temporales, p.e.unique_ptr
Como 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=赋值重载函数
// 示例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;
}
Entonces usaunique_ptr
El beneficio es que los usuarios pueden usar algo comounique_ptr<int> p2(move(p1));
declaración, se puede ver claramente quep1
Recursos transferidos ap2
,p1
No se retienen más recursos si los usa.auto_ptr
No será explícitamentemove
Escrito, la intención no es obvia. Si no comprende la capa subyacente, la usará incorrectamente.
Al mismo tiempo, desdeunique_ptr
Como 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
Los punteros inteligentes con recuento de referencias incluyen principalmenteshared_ptr
yweak_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.
Tome lo que se implementó anteriormenteCSmartPtr
Realice 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; // 指向该资源引用计数对象的指针
};
// 那么现在就不会报错了,不会对同一个资源释放多次
CSmartPtr<int> ptr1(new int());
CSmartPtr<int> ptr2(ptr1);
CSmartPtr<int> ptr3;
ptr3 = ptr2;
*ptr1 = 20;
cout << *ptr2 << " " << *ptr3 << endl; // 20 20
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_ptr
yweak_ptr
¡Es seguro para subprocesos!
shared_ptr
:poderosoPunteros inteligentes (puede cambiar el recuento de referencia de recursos)weak_ptr
:débilPunteros inteligentes (no cambie el recuento de referencias del recurso)Lo que implementamos en la sección anteriorCSmartPtr
Tambié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;
}
resultado de la operación:
A()
B()
2
2
Como puede ver, A y B no se destruyen, lo que significa que las referencias cruzadas causaránnew
Los recursos que salen no se pueden liberar, lo que provoca una fuga de recursos.
analizar:
afueramain
alcance de la función,pa
ypb
Se 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í dosnew
Los 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;
}
resultado de la operación:
A()
B()
1
1
~B()
~A()
se puede ver, afueramain
alcance de la función,pa
ypb
Los 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,new
El 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)
puede ser visto,weak_ptr
Los 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?
*
y->
Las funciones sobrecargadas de los operadores no pueden utilizar funciones similares a los punteros sin formatoSolució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;
}
A pesar de
weak_ptr
no 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_ptr
punto a esto),lock()
devolverá un puntero al objetoshared_ptr
; De lo contrario, se devolverá un valor vacío.shared_ptr
usar
lock()
Un escenario típico para los métodos es cuando se requiere acceso temporal.weak_ptr
el 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()
¡En este punto puedes ver que se llamó correctamente!
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;
}
en ejecuciónq->testA();
Cuando se hace esta declaración, elmain
El hilo ha destruido el objeto compartido, lo que obviamente no es razonable.
si quieres pasarq
Si el puntero quiere acceder al objeto A, necesita determinar si el objeto A está vivo. Si el objeto A está vivo, llame.testA
No hay problema con el método si el objeto A ha sido destruido, llámelo;testA
¡tiene un problema!Es decirq
Al 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;
}
resultado de la operación:
A()
非常好用的方法!!
~A()
Puedes ver que está funcionando.sp->testA();
,porquemain
El hilo llamadot1.join()
El método espera a que finalice el subproceso secundario.wp
aprobarlock
Promovido 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;
}
resultado de la operación:
A()
~A()
A对象已经析构,不能再访问!
Como puede ver, establecemos un alcance y también configuramost1
Para separar hilos, deje que los punteros inteligentesp
Cuando A se destruye fuera del alcance, se imprimirá en este momento.A对象已经析构,不能再访问!
, eso eswp
aprobarlock
No 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_ptr
yweak_ptr
Una aplicación típica.
Artículo de referencia:Comprensión profunda de los punteros inteligentes de C++