Vengo de un fondo Java y he comenzado a trabajar con objetos en C ++. Pero una cosa que se me ocurrió es que las personas a menudo usan punteros a los objetos en lugar de los objetos en sí, por ejemplo, esta declaración:
Object *myObject = new Object;
más bien que:
Object myObject;
O en lugar de usar una función, digamos testFunc()
, así:
myObject.testFunc();
tenemos que escribir:
myObject->testFunc();
Pero no puedo entender por qué deberíamos hacerlo de esta manera. Supongo que tiene que ver con la eficiencia y la velocidad, ya que tenemos acceso directo a la dirección de memoria. Estoy en lo cierto?
Respuestas:
Es muy lamentable que vea la asignación dinámica tan a menudo. Eso solo muestra cuántos malos programadores de C ++ hay.
En cierto sentido, tiene dos preguntas agrupadas en una. La primera es ¿cuándo debemos usar la asignación dinámica (usando
new
)? La segunda es ¿cuándo debemos usar punteros?El mensaje importante para llevar a casa es que siempre debe usar la herramienta adecuada para el trabajo . En casi todas las situaciones, hay algo más apropiado y más seguro que realizar una asignación dinámica manual y / o usar punteros sin procesar.
Asignación dinámica
En su pregunta, ha demostrado dos formas de crear un objeto. La principal diferencia es la duración del almacenamiento del objeto. Al hacerlo
Object myObject;
dentro de un bloque, el objeto se crea con una duración de almacenamiento automática, lo que significa que se destruirá automáticamente cuando salga del alcance. Cuando lo hacenew Object()
, el objeto tiene una duración de almacenamiento dinámico, lo que significa que permanece vivo hasta que lo explicitedelete
. Solo debe usar la duración del almacenamiento dinámico cuando lo necesite. Es decir, siempre debe preferir crear objetos con una duración de almacenamiento automática cuando pueda .Las dos situaciones principales en las que puede requerir una asignación dinámica:
Cuando realmente requiera una asignación dinámica, debe encapsularla en un puntero inteligente o en algún otro tipo que realice RAII (como los contenedores estándar). Los punteros inteligentes proporcionan semántica de propiedad de objetos asignados dinámicamente. Echa un vistazo a
std::unique_ptr
ystd::shared_ptr
, por ejemplo. Si los usa de manera adecuada, puede evitar casi por completo realizar su propia administración de memoria (consulte la Regla de cero ).Punteros
Sin embargo, existen otros usos más generales para los punteros sin procesar más allá de la asignación dinámica, pero la mayoría tiene alternativas que debería preferir. Como antes, siempre prefiera las alternativas a menos que realmente necesite punteros .
Necesita semántica de referencia . A veces, desea pasar un objeto utilizando un puntero (independientemente de cómo se asignó) porque desea que la función a la que lo pasa tenga acceso a ese objeto específico (no una copia del mismo). Sin embargo, en la mayoría de las situaciones, debe preferir los tipos de referencia a los punteros, porque esto es específicamente para lo que están diseñados. Tenga en cuenta que esto no se trata necesariamente de extender la vida útil del objeto más allá del alcance actual, como en la situación 1 anterior. Como antes, si está de acuerdo con pasar una copia del objeto, no necesita semántica de referencia.
Necesitas polimorfismo . Solo puede llamar a las funciones polimórficamente (es decir, según el tipo dinámico de un objeto) a través de un puntero o referencia al objeto. Si ese es el comportamiento que necesita, entonces necesita usar punteros o referencias. Nuevamente, se deben preferir las referencias.
Desea representar que un objeto es opcional al permitir
nullptr
que se pase uno cuando se omite el objeto. Si se trata de un argumento, debería preferir utilizar argumentos predeterminados o sobrecargas de funciones. De lo contrario, debe usar preferiblemente un tipo que encapsule este comportamiento, comostd::optional
(introducido en C ++ 17 - con estándares anteriores de C ++, useboost::optional
).Desea desacoplar unidades de compilación para mejorar el tiempo de compilación . La propiedad útil de un puntero es que solo necesita una declaración directa del tipo señalado (para usar realmente el objeto, necesitará una definición). Esto le permite desacoplar partes de su proceso de compilación, lo que puede mejorar significativamente el tiempo de compilación. Ver el idioma Pimpl .
Necesita interactuar con una biblioteca C o una biblioteca de estilo C. En este punto, te ves obligado a usar punteros en bruto. Lo mejor que puede hacer es asegurarse de que solo suelte sus punteros crudos en el último momento posible. Puede obtener un puntero sin formato a partir de un puntero inteligente, por ejemplo, utilizando su
get
función miembro. Si una biblioteca realiza una asignación para usted que espera que desasigne a través de un identificador, a menudo puede envolver el identificador en un puntero inteligente con un eliminador personalizado que desasignará el objeto adecuadamente.fuente
Object myObject(param1, etc...)
Hay muchos casos de uso para punteros.
Comportamiento polimórfico . Para los tipos polimórficos, se utilizan punteros (o referencias) para evitar el corte:
Semántica de referencia y evitando copiar . Para los tipos no polimórficos, un puntero (o una referencia) evitará copiar un objeto potencialmente costoso
Tenga en cuenta que C ++ 11 tiene una semántica de movimiento que puede evitar muchas copias de objetos caros en argumentos de función y como valores de retorno. Pero usar un puntero definitivamente los evitará y permitirá múltiples punteros en el mismo objeto (mientras que un objeto solo se puede mover de una vez).
Adquisición de recursos . Crear un puntero a un recurso utilizando el
new
operador es un antipatrón en C ++ moderno. Utilice una clase de recurso especial (uno de los contenedores estándar) o un puntero inteligente (std::unique_ptr<>
ostd::shared_ptr<>
). Considerar:vs.
Un puntero en bruto solo debe usarse como una "vista" y no de ninguna manera involucrado en la propiedad, ya sea a través de la creación directa o implícitamente a través de valores de retorno. Consulte también estas preguntas y respuestas de las preguntas frecuentes de C ++ .
Control de vida útil más detallado Cada vez que se copia un puntero compartido (por ejemplo, como argumento de función), el recurso al que apunta se mantiene vivo. Los objetos normales (no creados por
new
usted, ya sea directamente por usted o dentro de una clase de recurso) se destruyen cuando salen del alcance.fuente
unique_ptr
/ mover la semánticahun(b)
también requiere el conocimiento de la firma a menos que esté bien con no saber que proporcionó el tipo incorrecto hasta la compilación. Si bien el problema de referencia generalmente no se detectará en el momento de la compilación y tomaría más esfuerzo depurarlo, si está verificando la firma para asegurarse de que los argumentos sean correctos, también podrá ver si alguno de los argumentos son referencias entonces el bit de referencia se convierte en algo no problemático (especialmente cuando se usan IDEs o editores de texto que muestran la firma de las funciones seleccionadas). Además,const&
.Hay muchas respuestas excelentes a esta pregunta, incluidos los casos de uso importantes de declaraciones directas, polimorfismo, etc.
Examinemos la situación comparando los dos idiomas:
Java:
El equivalente más cercano a esto es:
C ++:
Veamos la forma alternativa de C ++:
La mejor manera de pensarlo es que, más o menos, Java maneja (implícitamente) punteros a objetos, mientras que C ++ puede manejar punteros a objetos o los propios objetos. Hay excepciones a esto: por ejemplo, si declara tipos "primitivos" de Java, son valores reales que se copian y no punteros. Entonces,
Java:
Dicho esto, el uso de punteros NO es necesariamente la forma correcta o incorrecta de manejar las cosas; sin embargo, otras respuestas lo han cubierto satisfactoriamente. Sin embargo, la idea general es que en C ++ tienes mucho más control sobre la vida útil de los objetos y sobre dónde vivirán.
Punto de referencia: la
Object * object = new Object()
construcción es en realidad lo más parecido a la semántica típica de Java (o C # para el caso).fuente
Object2 is now "dead"
: Creo que te refieresmyObject1
o más precisamentethe object pointed to by myObject1
.Object object1 = new Object(); Object object2 = new Object();
Es muy mal código. El segundo nuevo o el segundo constructor de objetos puede arrojar, y ahora se filtra el objeto1. Si está utilizandonew
s sin formato, debe envolver losnew
objetos ed en contenedores RAII lo antes posible.Otra buena razón para usar punteros sería para las declaraciones a futuro . En un proyecto lo suficientemente grande, realmente pueden acelerar el tiempo de compilación.
fuente
std::unique_ptr<T>
funciona con declaraciones a futuro deT
. Solo necesita asegurarse de que cuandostd::unique_ptr<T>
se llama al destructor del ,T
sea un tipo completo. Esto generalmente significa que su clase que contienestd::unique_ptr<T>
declara su destructor en el archivo de encabezado y lo implementa en el archivo cpp (incluso si la implementación está vacía).Prefacio
Java no se parece en nada a C ++, a diferencia del bombo publicitario. La máquina exagerada de Java quiere que creas que debido a que Java tiene una sintaxis similar a C ++, los lenguajes son similares. Nada puede estar más lejos de la verdad. Esta información errónea es parte de la razón por la cual los programadores de Java van a C ++ y usan una sintaxis similar a Java sin comprender las implicaciones de su código.
En adelante vamos
Por el contrario, en realidad. El montón es mucho más lento que la pila, porque la pila es muy simple en comparación con el montón. Las variables de almacenamiento automático (también conocidas como variables de pila) tienen sus destructores llamados una vez que salen del alcance. Por ejemplo:
Por otro lado, si usa un puntero asignado dinámicamente, su destructor debe llamarse manualmente.
delete
llama a este destructor por ti.Esto no tiene nada que ver con la
new
sintaxis prevalente en C # y Java. Se usan para propósitos completamente diferentes.Beneficios de la asignación dinámica
Uno de los primeros problemas con los que se encuentran muchos programadores de C ++ es que cuando aceptan entradas arbitrarias de los usuarios, solo se puede asignar un tamaño fijo para una variable de la pila. Tampoco puede cambiar el tamaño de las matrices. Por ejemplo:
Por supuesto, si usó un
std::string
en su lugar,std::string
internamente cambia su tamaño para que no sea un problema. Pero esencialmente la solución a este problema es la asignación dinámica. Puede asignar memoria dinámica en función de la entrada del usuario, por ejemplo:Debido a que el montón es mucho más grande que la pila, uno puede asignar / reasignar arbitrariamente tanta memoria como necesite, mientras que la pila tiene una limitación.
¿Cómo es esto un beneficio que pides? La respuesta quedará clara una vez que comprenda la confusión / mito detrás de las matrices y los punteros. Se supone comúnmente que son iguales, pero no lo son. Este mito proviene del hecho de que los punteros se pueden suscribir al igual que las matrices y debido a que las matrices decaen a punteros en el nivel superior en una declaración de función. Sin embargo, una vez que una matriz se descompone en un puntero, el puntero pierde su
sizeof
información. Porsizeof(pointer)
lo tanto , dará el tamaño del puntero en bytes, que generalmente es de 8 bytes en un sistema de 64 bits.No puede asignar a matrices, solo inicializarlas. Por ejemplo:
Por otro lado, puedes hacer lo que quieras con punteros. Desafortunadamente, debido a que la distinción entre punteros y matrices se agita a mano en Java y C #, los principiantes no entienden la diferencia.
Java y C # tienen funciones que le permiten tratar los objetos como otro, por ejemplo, utilizando la
as
palabra clave. Entonces, si alguien quisiera tratar unEntity
objeto como unPlayer
objeto, uno podría hacerlo.Player player = Entity as Player;
Esto es muy útil si tiene la intención de llamar a funciones en un contenedor homogéneo que solo debería aplicarse a un tipo específico. La funcionalidad se puede lograr de manera similar a continuación:Entonces, si solo Triángulos tuviera una función Rotar, sería un error del compilador si intentara llamarlo en todos los objetos de la clase. Usando
dynamic_cast
, puede simular laas
palabra clave. Para ser claros, si un lanzamiento falla, devuelve un puntero no válido. Entonces,!test
es esencialmente una abreviatura para verificar sitest
es NULL o un puntero no válido, lo que significa que el lanzamiento falló.Beneficios de las variables automáticas.
Después de ver todas las excelentes cosas que puede hacer la asignación dinámica, probablemente se esté preguntando por qué nadie NO usaría la asignación dinámica todo el tiempo. Ya te dije una razón, el montón es lento. Y si no necesita toda esa memoria, no debe abusar de ella. Así que aquí hay algunas desventajas sin ningún orden en particular:
Es propenso a errores. La asignación manual de memoria es peligrosa y es propenso a fugas. Si no dominas el uso del depurador o
valgrind
(una herramienta de pérdida de memoria), puedes sacarte el cabello de la cabeza. Afortunadamente, los modismos RAII y los punteros inteligentes alivian un poco esto, pero debes estar familiarizado con prácticas como La regla de tres y La regla de cinco. Es una gran cantidad de información, y los principiantes que no saben o no les importa caerán en esta trampa.No es necesario. A diferencia de Java y C #, donde es idiomático usar la
new
palabra clave en todas partes, en C ++, solo debe usarla si es necesario. La frase común dice, todo parece un clavo si tienes un martillo. Mientras que los principiantes que comienzan con C ++ tienen miedo de los punteros y aprenden a usar variables de pila por hábito, los programadores de Java y C # comienzan usando punteros sin entenderlo. Eso es literalmente pisar el pie equivocado. Debes abandonar todo lo que sabes porque la sintaxis es una cosa, aprender el idioma es otra.Una optimización que hacen muchos compiladores son las llamadas elisión y optimización del valor de retorno . Estas cosas pueden obviar copias innecesarias que son útiles para objetos que son muy grandes, como un vector que contiene muchos elementos. Normalmente, la práctica común es usar punteros para transferir la propiedad en lugar de copiar los objetos grandes para moverlos . Esto ha llevado al inicio de la semántica del movimiento y los punteros inteligentes .
Si está utilizando punteros, (N) RVO NO se produce. Es más beneficioso y menos propenso a errores aprovechar (N) RVO en lugar de devolver o pasar punteros si le preocupa la optimización. Pueden producirse fugas de error si el llamador de una función es responsable de
delete
crear un objeto asignado dinámicamente y tal. Puede ser difícil rastrear la propiedad de un objeto si los punteros se pasan como una papa caliente. Simplemente use variables de pila porque es más simple y mejor.fuente
{ std::string* s = new std::string; } delete s; // destructor called
... seguramente estodelete
no funcionará porque el compilador ya no sabrá qués
es.C ++ le ofrece tres formas de pasar un objeto: por puntero, por referencia y por valor. Java lo limita con el último (la única excepción son los tipos primitivos como int, boolean, etc.). Si desea utilizar C ++ no solo como un juguete extraño, será mejor que conozca la diferencia entre estas tres formas.
Java pretende que no existe un problema como "¿quién y cuándo debería destruir esto?". La respuesta es: El recolector de basura, genial y horrible. Sin embargo, no puede proporcionar una protección del 100% contra pérdidas de memoria (sí, Java puede perder memoria ). En realidad, GC te da una falsa sensación de seguridad. Cuanto más grande sea tu SUV, más largo será tu camino hacia el evacuador.
C ++ te deja cara a cara con la gestión del ciclo de vida del objeto. Bueno, hay medios para lidiar con eso ( familia de punteros inteligentes , QObject en Qt y así sucesivamente), pero ninguno de ellos se puede usar de manera 'dispara y olvida' como GC: siempre debes tener en cuenta el manejo de la memoria. No solo deberías preocuparte por destruir un objeto, también debes evitar destruir el mismo objeto más de una vez.
¿Todavía no tienes miedo? Ok: referencias cíclicas, trátalos tú mismo, humano. Y recuerda: mata cada objeto con precisión una vez, a los tiempos de ejecución de C ++ no nos gustan los que se meten con los cadáveres, dejamos a los muertos en paz.
Entonces, volviendo a tu pregunta.
Cuando pasa su objeto por valor, no por puntero o por referencia, copia el objeto (todo el objeto, ya sea un par de bytes o un gran volcado de la base de datos; es lo suficientemente inteligente como para evitar este último, no t you?) cada vez que haces '='. Y para acceder a los miembros del objeto, usa '.' (punto).
Cuando pasa su objeto por puntero, copia solo unos pocos bytes (4 en sistemas de 32 bits, 8 en sistemas de 64 bits), a saber, la dirección de este objeto. Y para mostrar esto a todos, utiliza este elegante operador '->' cuando accedes a los miembros. O puede usar la combinación de '*' y '.'.
Cuando usa referencias, obtiene el puntero que pretende ser un valor. Es un puntero, pero accede a los miembros a través de '.'.
Y, para volverte loco una vez más: cuando declaras varias variables separadas por comas, entonces (mira las manos):
Ejemplo:
fuente
std::auto_ptr
está en desuso, por favor no lo use.En C ++, los objetos asignados en la pila (usando la
Object object;
instrucción dentro de un bloque) solo vivirán dentro del alcance en el que se declaran. Cuando el bloque de código finaliza la ejecución, el objeto declarado se destruye. Mientras que si asigna memoria en el montón, usandoObject* obj = new Object()
, continúan viviendo en el montón hasta que llamedelete obj
.Crearía un objeto en el montón cuando quisiera usar el objeto no solo en el bloque de código que lo declaró / asignó.
fuente
Object obj
no siempre está en la pila, por ejemplo, variables globales o miembros.Compararé cómo funciona dentro del cuerpo de la función si usa:
Dentro de la función,
myObject
se destruirá una vez que esta función regrese. Esto es útil si no necesita su objeto fuera de su función. Este objeto se colocará en la pila de subprocesos actual.Si escribe dentro del cuerpo de la función:
entonces la instancia de clase de objeto señalada por
myObject
no se destruirá una vez que finalice la función, y la asignación esté en el montón.Ahora, si usted es un programador de Java, el segundo ejemplo está más cerca de cómo funciona la asignación de objetos en Java. Esta línea:
Object *myObject = new Object;
es equivalente a java:Object myObject = new Object();
. La diferencia es que bajo java myObject se recolectará basura, mientras que bajo c ++ no se liberará, debe llamar explícitamente a `delete myObject; ' de lo contrario, introducirá pérdidas de memoria.Desde c ++ 11 puede usar formas seguras de asignaciones dinámicas:
new Object
almacenando valores en shared_ptr / unique_ptr.Además, los objetos se almacenan muy a menudo en contenedores, como mapas-s o vectores-s, administrarán automáticamente una vida útil de sus objetos.
fuente
then myObject will not get destroyed once function ends
Absolutamente lo hará.myObject
seguirá siendo destruido, al igual que cualquier otra variable local. La diferencia es que su valor es un puntero a un objeto, no el objeto en sí mismo, y la destrucción de un puntero tonto no afecta a su puntero. Entonces el objeto sobrevivirá a dicha destrucción.Técnicamente es un problema de asignación de memoria, sin embargo, aquí hay dos aspectos más prácticos de esto. Tiene que ver con dos cosas: 1) Alcance, cuando define un objeto sin puntero, ya no podrá acceder a él después del bloque de código en el que está definido, mientras que si define un puntero con "nuevo", entonces puede acceder desde cualquier lugar donde tenga un puntero a esta memoria hasta que llame "eliminar" en el mismo puntero. 2) Si desea pasar argumentos a una función, quiere pasar un puntero o una referencia para ser más eficiente. Cuando pasa un Objeto, el objeto se copia; si se trata de un objeto que utiliza mucha memoria, puede consumir CPU (por ejemplo, copia un vector lleno de datos). Cuando pasa un puntero, todo lo que pasa es un int (dependiendo de la implementación, pero la mayoría de ellos son un int).
Aparte de eso, debe comprender que "nuevo" asigna memoria en el montón que debe liberarse en algún momento. Cuando no tenga que usar "nuevo", le sugiero que use una definición de objeto regular "en la pila".
fuente
Bueno, la pregunta principal es ¿Por qué debería usar un puntero en lugar del objeto en sí? Y mi respuesta es que (casi) nunca debe usar el puntero en lugar del objeto, porque C ++ tiene referencias , es más seguro que los punteros y garantiza el mismo rendimiento que los punteros.
Otra cosa que mencionaste en tu pregunta:
¿Como funciona? Crea un puntero de
Object
tipo, asigna memoria para adaptarse a un objeto y llama al constructor predeterminado, suena bien, ¿verdad? Pero en realidad no es tan bueno, si asignó memoria dinámicamente (palabra clave utilizadanew
), también debe liberar memoria manualmente, eso significa que en el código debe tener:Esto llama al destructor y libera memoria, parece fácil, sin embargo, en grandes proyectos puede ser difícil detectar si un hilo liberó memoria o no, pero para ese propósito puede probar punteros compartidos , estos disminuyen ligeramente el rendimiento, pero es mucho más fácil trabajar con ellos. ellos.
Y ahora se acabó alguna introducción y volvemos a la pregunta.
Puede usar punteros en lugar de objetos para obtener un mejor rendimiento mientras transfiere datos entre funciones.
Eche un vistazo, tiene
std::string
(también es un objeto) y contiene mucha información, por ejemplo, XML grande, ahora necesita analizarlo, pero para eso tiene una funciónvoid foo(...)
que puede declararse de diferentes maneras:void foo(std::string xml);
En este caso, copiará todos los datos de su variable a la pila de funciones, lleva un tiempo, por lo que su rendimiento será bajo.void foo(std::string* xml);
En este caso, pasará el puntero al objeto, a la misma velocidad que lasize_t
variable de paso , sin embargo, esta declaración es propensa a errores, ya que puede pasar elNULL
puntero o un puntero no válido. Los punteros generalmente se usanC
porque no tiene referencias.void foo(std::string& xml);
Aquí pasa la referencia, básicamente es lo mismo que pasar el puntero, pero el compilador hace algunas cosas y no puede pasar una referencia no válida (en realidad, es posible crear una situación con una referencia no válida, pero está engañando al compilador).void foo(const std::string* xml);
Aquí es lo mismo que el segundo, solo el valor del puntero no se puede cambiar.void foo(const std::string& xml);
Aquí es lo mismo que el tercero, pero el valor del objeto no se puede cambiar.Lo que más quiero mencionar es que puede usar estas 5 formas de pasar datos sin importar la forma de asignación que haya elegido (con
new
o regular ).Otra cosa para mencionar, cuando crea objetos de manera regular , asigna memoria en la pila, pero mientras la crea con el
new
montón. Asignar la pila es mucho más rápido, pero es un poco pequeño para matrices de datos realmente grandes, por lo que si necesita un objeto grande, debe usar el montón, porque puede obtener un desbordamiento de la pila, pero generalmente este problema se resuelve usando contenedores STL y recuerdestd::string
también es contenedor, algunos chicos lo olvidaron :)fuente
Digamos que tiene
class A
ese contenido.class B
Cuando desee llamar a alguna función declass B
afueraclass A
, simplemente obtendrá un puntero a esta clase y podrá hacer lo que quiera y también cambiará el contextoclass B
en suclass A
Pero ten cuidado con el objeto dinámico
fuente
Hay muchos beneficios de usar punteros para objetar:
fuente
Esto se ha discutido extensamente, pero en Java todo es un puntero. No hace distinción entre las asignaciones de pila y montón (todos los objetos se asignan en el montón), por lo que no se da cuenta de que está utilizando punteros. En C ++, puede mezclar los dos, según sus requisitos de memoria. El rendimiento y el uso de memoria es más determinista en C ++ (duh).
fuente
Hacer esto creará una referencia a un Objeto (en el montón) que debe eliminarse explícitamente para evitar pérdidas de memoria .
Hacer esto creará un objeto (myObject) del tipo automático (en la pila) que se eliminará automáticamente cuando el objeto (myObject) salga del alcance.
fuente
Un puntero hace referencia directamente a la ubicación de la memoria de un objeto. Java no tiene nada como esto. Java tiene referencias que hacen referencia a la ubicación del objeto a través de tablas hash. No puede hacer nada como la aritmética de puntero en Java con estas referencias.
Para responder a su pregunta, es solo su preferencia. Prefiero usar la sintaxis similar a Java.
fuente
Con punteros ,
puede hablar directamente a la memoria.
puede evitar muchas pérdidas de memoria de un programa manipulando punteros.
fuente
Una razón para usar punteros es interactuar con las funciones de C. Otra razón es para ahorrar memoria; por ejemplo: en lugar de pasar un objeto que contiene muchos datos y tiene un constructor de copia intensivo en procesador a una función, simplemente pase un puntero al objeto, ahorrando memoria y velocidad, especialmente si está en un bucle, sin embargo, un referencia sería mejor en ese caso, a menos que esté utilizando una matriz de estilo C.
fuente
En áreas donde la utilización de la memoria es primordial, los punteros son útiles. Por ejemplo, considere un algoritmo minimax, donde se generarán miles de nodos usando la rutina recursiva, y luego úselos para evaluar el siguiente mejor movimiento en el juego, la capacidad de desasignar o restablecer (como en los punteros inteligentes) reduce significativamente el consumo de memoria. Mientras que la variable sin puntero continúa ocupando espacio hasta que su llamada recursiva devuelve un valor.
fuente
Incluiré un caso de uso importante del puntero. Cuando está almacenando algún objeto en la clase base, pero podría ser polimórfico.
Entonces, en este caso no puede declarar bObj como un objeto directo, debe tener un puntero.
fuente
"La necesidad es la madre de la invención." La diferencia más importante que me gustaría señalar es el resultado de mi propia experiencia de codificación. A veces necesitas pasar objetos a funciones. En ese caso, si su objeto es de una clase muy grande, pasarlo como un objeto copiará su estado (que puede que no desee ... Y PUEDE SER GRANDE SOBRE LA CARGA), lo que da como resultado una sobrecarga de copia del objeto. Tamaño de 4 bytes (suponiendo 32 bits). Otras razones ya se mencionan anteriormente ...
fuente
std::string test;
que tenemosvoid func(const std::string &) {}
, pero menos que la función tiene que cambiar la entrada, en cuyo caso te recomiendo el uso de punteros (de modo que cualquiera que lea el código hace previo aviso&
, y entiende la función, puede cambiar su entrada)Ya hay muchas respuestas excelentes, pero déjame darte un ejemplo:
Tengo una clase de artículo simple:
Hago un vector para contener un montón de ellos.
std::vector<Item> inventory;
Creo un millón de objetos Item, y los empujo hacia el vector. Ordeno el vector por nombre y luego hago una búsqueda binaria iterativa simple para un nombre de elemento en particular. Pruebo el programa y me lleva más de 8 minutos terminar de ejecutarlo. Luego cambio mi vector de inventario así:
std::vector<Item *> inventory;
... y crear mi millón de objetos Item a través de nuevo. Los ÚNICOS cambios que hago en mi código son usar los punteros a los Elementos, excepto un ciclo que agrego para la limpieza de la memoria al final. Ese programa se ejecuta en menos de 40 segundos, o mejor que un aumento de velocidad de 10x. EDITAR: El código está en http://pastebin.com/DK24SPeW. Con las optimizaciones del compilador, muestra solo un aumento de 3.4x en la máquina en la que lo probé, lo que aún es considerable.
fuente
push_back
. Por supuesto esto copia. Debería haber estadoemplace
en el lugar al crear sus objetos (a menos que necesite que se almacenen en caché en otro lugar).