ejemplos - c++ online
¿Es una buena práctica usar siempre punteros inteligentes? (10)
Dadas las diversas ediciones, tengo la impresión de que sería útil un resumen completo.
1. Cuando no para
Hay dos situaciones en las que no debe usar punteros inteligentes.
El primero es exactamente la misma situación en la que no debería usar una clase de C++
de hecho. IE: Límite DLL si no ofrece el código fuente al cliente. Deja decir anecdótico.
El segundo ocurre mucho más a menudo: el administrador inteligente significa propiedad . Puede usar punteros para señalar los recursos existentes sin administrar su tiempo de vida, por ejemplo:
void notowner(const std::string& name)
{
Class* pointer(0);
if (name == "cat")
pointer = getCat();
else if (name == "dog")
pointer = getDog();
if (pointer) doSomething(*pointer);
}
Este ejemplo está restringido. Pero un puntero es semánticamente diferente de una referencia en que puede apuntar a una ubicación no válida (el puntero nulo). En este caso, está perfectamente bien no usar un puntero inteligente en su lugar, porque no desea administrar la vida útil del objeto.
2. Gerentes inteligentes
A menos que esté escribiendo una clase de administrador inteligente, si usa la palabra clave delete
, está haciendo algo mal.
Es un punto de vista controvertido, pero después de haber revisado tantos ejemplos de códigos defectuosos, ya no corro riesgos. Entonces, si escribe algo new
, necesita un administrador inteligente para la memoria recién asignada. Y lo necesitas en este momento.
¡No significa que eres menos un programador! Por el contrario, volver a utilizar el código que se ha demostrado que funciona en lugar de reinventar la rueda una y otra vez es una habilidad clave.
Ahora, comienza la verdadera dificultad: ¿qué administrador inteligente?
3. Punteros inteligentes
Hay varios indicadores inteligentes de allí, con varias características.
Skipping std::auto_ptr
que generalmente debe evitar (su semántica de copia está atornillada).
-
scoped_ptr
: sin gastos generales, no se puede copiar ni mover. -
unique_ptr
: sin gastos generales, no se puede copiar, se puede mover. -
shared_ptr
/weak_ptr
: algunos gastos generales (recuento de referencias), se pueden copiar.
Por lo general, intente utilizar scoped_ptr
o unique_ptr
. Si necesita varios propietarios intente cambiar el diseño. Si no puede cambiar el diseño y realmente necesita varios propietarios, use un shared_ptr
, pero tenga cuidado con los ciclos de referencias que deberían romperse utilizando un weak_ptr
en algún lugar en el medio.
4. Contenedores inteligentes
Muchos punteros inteligentes no están destinados a ser copiados, por lo tanto, su uso con los contenedores STL está algo comprometido.
En lugar de recurrir a shared_ptr
y su sobrecarga, use contenedores inteligentes del Boost Pointer Container . Emulan la interfaz de los contenedores STL clásicos pero almacenan los indicadores que poseen.
5. Rolando tu propio
Hay situaciones en las que puede desear rodar su propio administrador inteligente. Verifica que no solo hayas omitido alguna función en las bibliotecas que estás usando de antemano.
Escribir un administrador inteligente en presencia de excepciones es bastante difícil. Por lo general, no puede suponer que la memoria está disponible (la new
puede fallar) o que Copy Constructor
''s tiene la garantía de no throw
.
Puede ser aceptable, en cierto modo, ignorar la excepción std::bad_alloc
e imponer que Copy Constructor
s de un número de ayudantes no falle ... después de todo, eso es lo que boost::shared_ptr
hace por su parámetro de plantilla D
eliminación.
Pero no lo recomendaría, especialmente para un principiante. Es un problema complicado, y es probable que no notes los errores en este momento.
6. Ejemplos
// For the sake of short code, avoid in real code ;)
using namespace boost;
// Example classes
// Yes, clone returns a raw pointer...
// it puts the burden on the caller as for how to wrap it
// It is to obey the `Cloneable` concept as described in
// the Boost Pointer Container library linked above
struct Cloneable
{
virtual ~Cloneable() {}
virtual Cloneable* clone() const = 0;
};
struct Derived: Cloneable
{
virtual Derived* clone() const { new Derived(*this); }
};
void scoped()
{
scoped_ptr<Cloneable> c(new Derived);
} // memory freed here
// illustration of the moved semantics
unique_ptr<Cloneable> unique()
{
return unique_ptr<Cloneable>(new Derived);
}
void shared()
{
shared_ptr<Cloneable> n1(new Derived);
weak_ptr<Cloneable> w = n1;
{
shared_ptr<Cloneable> n2 = n1; // copy
n1.reset();
assert(n1.get() == 0);
assert(n2.get() != 0);
assert(!w.expired() && w.get() != 0);
} // n2 goes out of scope, the memory is released
assert(w.expired()); // no object any longer
}
void container()
{
ptr_vector<Cloneable> vec;
vec.push_back(new Derived);
vec.push_back(new Derived);
vec.push_back(
vec.front().clone() // Interesting semantic, it is dereferenced!
);
} // when vec goes out of scope, it clears up everything ;)
Encuentro que los punteros inteligentes son mucho más cómodos que los punteros sin procesar. Entonces, ¿es una buena idea usar siempre punteros inteligentes? (Tenga en cuenta que procedo de Java y, por lo tanto, no me gusta mucho la idea de la gestión explícita de la memoria. Por lo tanto, a menos que existan algunos problemas graves de rendimiento con los indicadores inteligentes, me gustaría seguir con ellos).
Nota: Aunque procedo de Java, entiendo muy bien la implementación de punteros inteligentes y los conceptos de RAII. Así que puedes dar por hecho este conocimiento de mi lado cuando publiques una respuesta. Utilizo la asignación estática casi en todas partes y uso punteros solo cuando es necesario. Mi pregunta es simplemente: ¿siempre puedo usar punteros inteligentes en lugar de punteros sin procesar?
En general, no, no puedes usar punteros inteligentes siempre. Por ejemplo, cuando usa otros marcos que no usan puntero inteligente (como Qt), también debe usar punteros sin formato.
En muchas situaciones, creo que definitivamente son el camino a seguir (menos código de limpieza desordenado, menor riesgo de fugas, etc.). Sin embargo, hay algún gasto extra muy leve. Si estuviera escribiendo algún código que tuviera que ser lo más rápido posible (por ejemplo, un ciclo cerrado que tuviera que hacer una asignación y uno gratis), probablemente no usaría un puntero inteligente con la esperanza de obtener un poco más de velocidad. Pero dudo que haría una diferencia medible en la mayoría de las situaciones.
Es. El puntero inteligente es una de las piedras angulares del viejo ecosistema Cocoa (Touch). Creo que sigue impactando a lo nuevo.
Los punteros inteligentes realizan una gestión de memoria explícita, y si no entiende cómo lo están haciendo, se encontrará con un mundo de problemas cuando programe con C ++. Y recuerde que la memoria no es el único recurso que administran.
Pero para responder a su pregunta, debería preferir los smart-punteros como primera aproximación a una solución, pero posiblemente esté preparado para deshacerse de ellos cuando sea necesario. Nunca debe usar punteros (ni ningún tipo) o asignación dinámica cuando pueda evitarse. Por ejemplo:
string * s1 = new string( "foo" ); // bad
string s2( "bar" ); // good
Editar: para responder a su pregunta suplementaria "¿Puedo usar siempre punteros inteligentes en lugar de punteros sin procesar? Entonces, no, no puede. Si (por ejemplo) necesita implementar su propia versión de operador nueva, tendría que hacer que devuelva un puntero, no un puntero inteligente.
Mi opinión sobre los punteros inteligentes: EXCELENTE cuando es difícil saber cuándo podría ocurrir la desasignación (por ejemplo, dentro de un bloque try / catch, o dentro de una función que llama a una función (¡o incluso a un constructor!) Que podría descartarte de tu función actual) , o agregando una mejor administración de memoria a una función que tiene devoluciones en todas partes del código. O poner punteros en contenedores.
Los indicadores inteligentes, sin embargo, tienen un costo que quizás no desee pagar en todo su programa. Si la administración de la memoria es fácil de hacer a mano ("Hmm, sé que cuando termine esta función, necesito eliminar estos tres indicadores, y sé que esta función se ejecutará hasta su finalización"), entonces ¿por qué desperdiciar los ciclos que tiene la computadora? ¿eso?
Por lo general, no debe usar punteros (inteligentes o de otro tipo) si no los necesita. Mejor hacer que las variables locales, los miembros de la clase, los elementos vectoriales y los elementos similares sean objetos normales en lugar de punteros a los objetos. (Debido a que vienes de Java, probablemente tengas la tentación de asignar todo con new
, lo cual no es recomendable).
Este enfoque (" RAII ") le evita preocuparse por los punteros la mayor parte del tiempo.
Cuando tiene que usar punteros, depende de la situación y de por qué necesita punteros, pero generalmente se pueden usar punteros inteligentes. Puede que no sea siempre (en negrita) la mejor opción, pero esto depende de la situación específica.
Sí, PERO he ido varios proyectos sin el uso de un puntero inteligente o punteros. Es una buena práctica usar contenedores como deque, list, map, etc. Como alternativa, uso referencias cuando sea posible. En lugar de pasar un puntero, paso una referencia o referencia constante y es casi siempre ilógico eliminar / liberar una referencia, así que nunca tengo problemas allí (normalmente los creo en la pila escribiendo { Class class; func(class, ref2, ref3); }
Si está manejando un recurso, siempre debe usar técnicas RAII, en el caso de la memoria significa usar una u otra forma de un puntero inteligente (nota: inteligente, no shared_ptr
, elija el puntero inteligente que sea más apropiado para su uso específico) caso). Es la única forma de evitar fugas en presencia de excepciones.
Todavía hay casos en los que los punteros crudos son necesarios, cuando la gestión de recursos no se maneja a través del puntero. En particular, son la única forma de tener una referencia reiniciable. Piense en mantener una referencia en un objeto cuya duración no se puede manejar explícitamente (atributo de miembro, objeto en la pila). Pero ese es un caso muy específico que solo he visto una vez en código real. En la mayoría de los casos, usar un shared_ptr
es un mejor enfoque para compartir un objeto.
Un buen momento para no utilizar punteros inteligentes es en el límite de interfaz de una DLL. No sabe si otros ejecutables se compilarán con el mismo compilador / bibliotecas. La convención de llamadas a DLL de su sistema no especificará qué clases estándar o TR1 parecen, incluidos los indicadores inteligentes.
Dentro de un archivo ejecutable o biblioteca, si desea representar la propiedad de la punta, entonces los punteros inteligentes son en promedio la mejor manera de hacerlo. Así que está bien querer usarlos siempre con preferencia a los crudos. Si usted realmente puede usarlos siempre es otro asunto.
Para un ejemplo concreto, cuando no suponga que está escribiendo una representación de un gráfico genérico, con vértices representados por objetos y bordes representados por punteros entre los objetos. Los punteros inteligentes usuales no lo ayudarán: los gráficos pueden ser cíclicos y ningún nodo en particular puede ser responsable de la administración de la memoria de otros nodos, por lo que los punteros compartidos y débiles son insuficientes. Por ejemplo, puede poner todo en un vector y usar índices en lugar de punteros, o poner todo en un deque y usar punteros crudos. Puede usar shared_ptr
si lo desea, pero no agregará nada excepto la sobrecarga. O podrías buscar el GC marca-sweep.
Un caso más marginal: prefiero ver que las funciones toman un parámetro por puntero o referencia, y prometo no retener un puntero o referencia a él , en lugar de tomar un shared_ptr
y dejarlo preguntándose si tal vez retengan una referencia después de que regresen, tal vez si modifica la referencia y otra vez, romperá algo, etc. No conservar referencias es algo que a menudo no está documentado explícitamente, simplemente es evidente. Tal vez no debería, pero lo hace. Los indicadores inteligentes implican algo acerca de la propiedad, y la falsa implicación de que puede ser confuso. Entonces, si su función toma un shared_ptr
, asegúrese de documentar si puede retener una referencia o no.