Pregunta ¿Por qué debería usar un puntero en lugar del objeto en sí?


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 para objetos en lugar de los objetos mismos, 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(), Me gusta esto:

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 la memoria. ¿Estoy en lo cierto?


1333
2018-03-03 11:54


origen


Respuestas:


Es muy desafortunado que vea la asignación dinámica tan a menudo. Eso solo muestra cuántos programadores malos de C ++ hay.

En cierto sentido, tiene dos preguntas agrupadas en una sola. El primero es cuándo deberíamos usar la asignación dinámica (usando new)? El segundo es cuando deberíamos usar punteros?

El importante mensaje para llevar a casa es que deberías use siempre la herramienta adecuada para el trabajo. En casi todas las situaciones, hay algo más apropiado y seguro que realizar asignaciones dinámicas manuales y / o usar punteros crudos.

Asignación dinámica

En su pregunta, ha demostrado dos formas de crear un objeto. La principal diferencia es la duración de almacenamiento del objeto. Cuando haces 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 se salga del alcance. Cuando tu lo hagas new Object(), el objeto tiene una duración de almacenamiento dinámica, lo que significa que permanece activo hasta que explícitamente delete eso. Solo debe usar la duración de almacenamiento dinámico cuando lo necesite. Es decir, debieras siempre Prefiere crear objetos con duración de almacenamiento automático cuando puedas.

Las dos situaciones principales en las que puede necesitar asignación dinámica:

  1. Necesita el objeto para sobrevivir el alcance actual - ese objeto específico en esa ubicación de memoria específica, no una copia de él. Si está de acuerdo con copiar / mover el objeto (la mayoría de las veces debe hacerlo), debería preferir un objeto automático.
  2. Necesita asignar mucha memoria, que puede llenar fácilmente la pila. Sería bueno si no tuviéramos que preocuparnos por esto (la mayoría de las veces no debería tener que hacerlo), ya que está realmente fuera del alcance de C ++, pero desafortunadamente tenemos que lidiar con la realidad de los sistemas que estamos desarrollando para.

Cuando necesita absolutamente la asignación dinámica, debe encapsularla en un puntero inteligente u otro tipo que funcione RAII (como los contenedores estándar). Los punteros inteligentes proporcionan una semántica de propiedad de los objetos asignados dinámicamente. Echa un vistazo a std::unique_ptr y std::shared_ptr, por ejemplo. Si los usa de forma adecuada, puede evitar casi por completo realizar su propia gestión de memoria (consulte la Regla de cero)

Punteros

Sin embargo, hay otros usos más generales para punteros crudos más allá de la asignación dinámica, pero la mayoría tienen alternativas que usted debería preferir. Como antes, Siempre prefiero las alternativas a menos que realmente necesite punteros.

  1. Necesitas semántica de referencia. A veces desea pasar un objeto usando un puntero (independientemente de cómo se asignó) porque desea que la función a la que lo está transfiriendo tenga acceso a ese objeto específico (no una copia). Sin embargo, en la mayoría de las situaciones, debería 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 implica necesariamente 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.

  2. Necesitas polimorfismo. Solo puede llamar funciones polimórficamente (es decir, de acuerdo con 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. De nuevo, las referencias deben ser preferidas.

  3. Desea representar que un objeto es opcional permitiendo una nullptr para pasar cuando se está omitiendo el objeto. Si se trata de un argumento, debería preferir usar argumentos predeterminados o sobrecargas de funciones. De lo contrario, debería preferir usar un tipo que encapsule este comportamiento, como std::optional (introducido en C ++ 17 - con los estándares anteriores de C ++, uso boost::optional)

  4. Desea desacoplar unidades de compilación para mejorar el tiempo de compilación. La propiedad útil de un puntero es que solo requiere una declaración directa del tipo apuntado (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 Pimpl idioma.

  5. Necesita interactuar con una biblioteca C o una biblioteca de estilo C En este punto, estás obligado a usar punteros crudos. Lo mejor que puede hacer es asegurarse de que solo suelta sus punteros crudos en el último momento posible. Puede obtener un puntero sin formato desde un puntero inteligente, por ejemplo, utilizando su get función miembro Si una biblioteca realiza alguna asignación para usted que espera que desasigne a través de un identificador, a menudo puede ajustar el manejador en un puntero inteligente con un eliminador personalizado que desasignará el objeto de manera apropiada.


1346
2018-03-03 12:06



Hay muchos casos de uso para punteros.

Comportamiento polimórfico. Para tipos polimórficos, los punteros (o referencias) se utilizan para evitar rebanar:

class Base { ... };
class Derived : public Base { ... };

void fun(Base b) { ... }
void gun(Base* b) { ... }
void hun(Base& b) { ... }

Derived d;
fun(d);    // oops, all Derived parts silently "sliced" off
gun(&d);   // OK, a Derived object IS-A Base object
hun(d);    // also OK, reference also doesn't slice

Semántica de referencia y evitar copiar. Para tipos no polimórficos, un puntero (o una referencia) evitará copiar un objeto potencialmente caro

Base b;
fun(b);  // copies b, potentially expensive 
gun(&b); // takes a pointer to b, no copying
hun(b);  // regular syntax, behaves as a pointer

Tenga en cuenta que C ++ 11 tiene una semántica de movimiento que puede evitar muchas copias de objetos costosos en el argumento de función y como valores de retorno. Pero usar un puntero definitivamente evitará eso y permitirá punteros múltiples 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 usando el new el operador es un anti-patrón en C ++ moderno. Use una clase de recurso especial (uno de los contenedores estándar) o una puntero inteligente (std::unique_ptr<> o std::shared_ptr<>) Considerar:

{
    auto b = new Base;
    ...       // oops, if an exception is thrown, destructor not called!
    delete b;
}

vs.

{
    auto b = std::make_unique<Base>();
    ...       // OK, now exception safe
}

Un puntero sin formato solo debe usarse como una "vista" y no debe estar involucrado de ninguna manera en la propiedad, ya sea a través de la creación directa o implícitamente mediante valores de retorno. Ver también este Q & A de las preguntas frecuentes de C ++.

Control de tiempo de vida más detallado Cada vez que se copia un puntero compartido (por ejemplo, como un argumento de función), el recurso al que apunta se mantiene activo. Objetos regulares (no creados por new, ya sea directamente por usted o dentro de una clase de recursos) se destruyen cuando se sale del alcance.


155
2018-03-06 18:40



Hay muchas respuestas excelentes a esta pregunta, incluidos los casos de uso importantes de declaraciones directas, polimorfismo, etc., pero siento que una parte del "alma" de su pregunta no recibe respuesta, es decir, qué significan las diferentes sintaxis en Java y C ++.

Examinemos la situación comparando los dos idiomas:

Java:

Object object1 = new Object(); //A new object is allocated by Java
Object object2 = new Object(); //Another new object is allocated by Java

object1 = object2; 
//object1 now points to the object originally allocated for object2
//The object originally allocated for object1 is now "dead" - nothing points to it, so it
//will be reclaimed by the Garbage Collector.
//If either object1 or object2 is changed, the change will be reflected to the other

El equivalente más cercano a esto, es:

C ++:

Object * object1 = new Object(); //A new object is allocated on the heap
Object * object2 = new Object(); //Another new object is allocated on the heap
delete object1;
//Since C++ does not have a garbage collector, if we don't do that, the next line would 
//cause a "memory leak", i.e. a piece of claimed memory that the app cannot use 
//and that we have no way to reclaim...

object1 = object2; //Same as Java, object1 points to object2.

Veamos la forma alternativa de C ++:

Object object1; //A new object is allocated on the STACK
Object object2; //Another new object is allocated on the STACK
object1 = object2;//!!!! This is different! The CONTENTS of object2 are COPIED onto object1,
//using the "copy assignment operator", the definition of operator =.
//But, the two objects are still different. Change one, the other remains unchanged.
//Also, the objects get automatically destroyed once the function returns...

La mejor manera de pensarlo es que, más o menos, Java maneja (implícitamente) los punteros a los objetos, mientras que C ++ puede manejar los punteros a los objetos o los objetos mismos. Hay excepciones a esto; por ejemplo, si declara tipos Java "primitivos", son valores reales que se copian y no punteros. Asi que,

Java:

int object1; //An integer is allocated on the stack.
int object2; //Another integer is allocated on the stack.
object1 = object2; //The value of object2 is copied to object1.

Dicho esto, usar 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 inicio: el Object * object = new Object() constructo es en realidad lo más parecido a la semántica típica de Java (o C # para el caso).


111
2018-03-03 14:34



Otra buena razón para usar punteros sería para declaraciones adelante. En un proyecto lo suficientemente grande, realmente pueden acelerar el tiempo de compilación.


73
2018-03-07 07:30



Prefacio

Java no se parece en nada a C ++, al contrario de la exageración. La máquina de hype de Java desea que creas que, debido a que Java tiene sintaxis similar a C ++, los idiomas son similares. Nada puede estar más lejos de la verdad. Esta desinformación es parte de la razón por la cual los programadores Java van a C ++ y usan una sintaxis similar a Java sin entender las implicaciones de su código.

Adelante vamos

Pero no puedo entender por qué deberíamos hacerlo de esta manera. Yo lo asumiría   tiene que ver con la eficiencia y la velocidad, ya que tenemos acceso directo a la   dirección de memoria ¿Estoy en lo cierto?

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:

{
    std::string s;
}
// s is destroyed here

Por otro lado, si utiliza un puntero asignado dinámicamente, su destructor debe invocarse manualmente. deletellama a este destructor por ti.

{
    std::string* s = new std::string;
}
delete s; // destructor called

Esto no tiene nada que ver con el new sintaxis prevalente en C # y Java. Se usan con propósitos completamente diferentes.

Beneficios de la asignación dinámica

1. No tienes que saber el tamaño de la matriz por adelantado

Uno de los primeros problemas que encuentran muchos programadores de C ++ es que cuando aceptan entradas arbitrarias de los usuarios, solo puede asignar un tamaño fijo para una variable de pila. Tampoco puedes cambiar el tamaño de las matrices. Por ejemplo:

char buffer[100];
std::cin >> buffer;
// bad input = buffer overflow

Por supuesto, si usaste una std::string en lugar, std::string se cambia el tamaño internamente, así que no debería ser 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:

int * pointer;
std::cout << "How many items do you need?";
std::cin >> n;
pointer = new int[n];

Nota al margen: Un error que muchos principiantes cometen es el uso de   matrices de longitud variable. Esta es una extensión de GNU y también una en Clang   porque reflejan muchas de las extensiones de GCC. Entonces el siguiente    int arr[n] no se debe confiar en

Debido a que el montón es mucho más grande que la pila, uno puede asignar / reasignar arbitrariamente la cantidad de memoria que necesita, mientras que la pila tiene una limitación.

2. Las matrices no son punteros

¿Cómo es esto un beneficio que preguntas? La respuesta quedará clara una vez que comprenda la confusión / mito detrás de las matrices y punteros. Comúnmente se supone que son lo mismo, pero no lo son. Este mito proviene del hecho de que los punteros se pueden suscribir al igual que las matrices y debido a las matrices decaen a los punteros en el nivel superior en una declaración de función. Sin embargo, una vez que una matriz se desintegra a un puntero, el puntero pierde su sizeof información. Asi que sizeof(pointer) 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:

int arr[5] = {1, 2, 3, 4, 5}; // initialization 
int arr[] = {1, 2, 3, 4, 5}; // The standard dictates that the size of the array
                             // be given by the amount of members in the initializer  
arr = { 1, 2, 3, 4, 5 }; // ERROR

Por otro lado, puedes hacer lo que quieras con punteros. Desafortunadamente, debido a que la distinción entre punteros y matrices se mueven manualmente en Java y C #, los principiantes no entienden la diferencia.

3. Polimorfismo

Java y C # tienen instalaciones que le permiten tratar objetos como otros, por ejemplo, utilizando el as palabra clave. Entonces, si alguien quisiera tratar un Entity objeto como un Player objeto, uno podría hacer 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:

std::vector<Base*> vector;
vector.push_back(&square);
vector.push_back(&triangle);
for (auto& e : vector)
{
     auto test = dynamic_cast<Triangle*>(e); // I only care about triangles
     if (!test) // not a triangle
        e.GenericFunction();
     else
        e.TriangleOnlyMagic();
}

Entonces, si solo Triángulos tuviera una función de Girar, sería un error de compilación si intentas invocarla en todos los objetos de la clase. Utilizando dynamic_cast, puedes simular as palabra clave. Para que quede claro, si un lanzamiento falla, devuelve un puntero no válido. Asi que !test es esencialmente una forma abreviada de verificar si test 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 grandes cosas que la asignación dinámica puede hacer, probablemente se pregunte por qué nadie usaría la asignación dinámica todo el tiempo. Ya te dije una razón, el montón es lento. Y si no necesitas toda esa memoria, no debes abusar de ella. Aquí hay algunas desventajas sin ningún orden en particular:

  • Es propenso a errores. La asignación manual de la memoria es peligrosa y usted es propenso a fugas. Si no dominas el uso del depurador o valgrind (una herramienta de pérdida de memoria), puede quitarse el pelo de la cabeza. Afortunadamente, los modismos RAII y los indicadores inteligentes alivian esto un poco, pero debes estar familiarizado con prácticas como The Rule Of Three y The Rule Of Five. Es mucha información para asimilar, 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 el new palabra clave en todas partes, en C ++, solo debe usarla si lo necesita. La frase común dice: todo parece un clavo si tienes un martillo. Mientras que los principiantes que comienzan con C ++ temen los indicadores y aprenden a usar variables de pila por hábito, los programadores Java y C # comienzo usando punteros sin entenderlo! Eso es, literalmente, caminar con el pie izquierdo. Debes abandonar todo lo que sabes porque la sintaxis es una cosa, aprender el idioma es otra.

1. (N) RVO - Aka, (Nombrado) Optimización del valor de retorno

Una optimización que hacen muchos compiladores se llama elisión y optimización del valor de retorno. Estas cosas pueden evitar copys innecesarios 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 propiedad en lugar de copiar los objetos grandes a movimiento ellos alrededor. Esto ha llevado al inicio de mover la semántica y punteros inteligentes.

Si está utilizando punteros, (N) RVO sí NO ocurrir. 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. Se pueden producir pérdidas de errores si la persona que llama de una función es responsable de deleteing un objeto dinámicamente asignado y tal. Puede ser difícil hacer un seguimiento de la propiedad de un objeto si se pasan punteros como una patata caliente. Simplemente use las variables de la pila porque es más simple y mejor.


62
2018-03-07 10:00



C ++ le brinda tres formas de pasar un objeto: por puntero, por referencia y por valor. Java te limita con el último (la única excepción son los tipos primitivos como int, boolean, etc.). Si desea usar C ++ no solo como un juguete extraño, entonces será mejor que conozca la diferencia entre estas tres formas.

Java pretende que no existe el problema de "quién y cuándo debería destruir esto". La respuesta es: The Garbage Collector, Great and Awful. Sin embargo, no puede proporcionar una protección del 100% contra fugas de memoria (sí, Java poder memoria de fuga) 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 la evacuación.

C ++ lo deja cara a cara con la administración del ciclo de vida del objeto. Bueno, hay medios para lidiar con eso (punteros inteligentes familia, QObject en Qt y así sucesivamente), pero ninguno de ellos se puede utilizar de manera "fuego y olvidar" como GC: debes siempretenga en cuenta el manejo de la memoria. No solo debes preocuparte por destruir un objeto, también debes evitar destruir el mismo objeto más de una vez.

¿No tienes miedo todavía? Ok: referencias cíclicas: trátelas 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 mezclan con los cadáveres, deja a los muertos en paz.

Entonces, volviendo a tu pregunta.

Cuando pasas tu objeto por valor, no por puntero ni por referencia, copias el objeto (el objeto completo, ya sea un par de bytes o un enorme volcado de base de datos; eres lo suficientemente inteligente como para evitarlo, no lo sé). ¿tú?) cada vez que haces '='. Y para acceder a los miembros del objeto, usa '.' (punto).

Cuando pasa el objeto por un puntero, copia unos pocos bytes (4 en los sistemas de 32 bits, 8 en los de 64 bits), es decir, la dirección de este objeto. Y para mostrar esto a todo el mundo, utilice este elegante operador '->' cuando acceda a los miembros. O puede usar la combinación de '*' y '.'.

Cuando utiliza 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 (observa las manos):

  • El tipo se le da a todos
  • El modificador de valor / puntero / referencia es individual

Ejemplo:

struct MyStruct
{
    int* someIntPointer, someInt; //here comes the surprise
    MyStruct *somePointer;
    MyStruct &someReference;
};

MyStruct s1; //we allocated an object on stack, not in heap

s1.someInt = 1; //someInt is of type 'int', not 'int*' - value/pointer modifier is individual
s1.someIntPointer = &s1.someInt;
*s1.someIntPointer = 2; //now s1.someInt has value '2'
s1.somePointer = &s1;
s1.someReference = s1; //note there is no '&' operator: reference tries to look like value
s1.somePointer->someInt = 3; //now s1.someInt has value '3'
*(s1.somePointer).someInt = 3; //same as above line
*s1.somePointer->someIntPointer = 4; //now s1.someInt has value '4'

s1.someReference.someInt = 5; //now s1.someInt has value '5'
                              //although someReference is not value, it's members are accessed through '.'

MyStruct s2 = s1; //'NO WAY' the compiler will say. Go define your '=' operator and come back.

//OK, assume we have '=' defined in MyStruct

s2.someInt = 0; //s2.someInt == 0, but s1.someInt is still 5 - it's two completely different objects, not the references to the same one

21
2018-03-03 12:00



En C ++, los objetos asignados en la pila (usando Object object; declaración dentro de un bloque) solo vivirá dentro del alcance en el que están declaradas. Cuando el bloque de código finaliza la ejecución, el objeto declarado se destruye. Mientras que si asigna memoria en montón, usando Object* obj = new Object(), continúan viviendo en montón hasta que llamas delete obj.

Crearía un objeto en montón cuando quisiera usar el objeto no solo en el bloque de código que lo declaró / asignó.


19
2018-03-03 12:19



Pero no puedo entender por qué deberíamos usarlo así?

Voy a comparar cómo funciona dentro del cuerpo de la función si usa:

Object myObject;

Dentro de la función, su myObject será destruido una vez que esta función regrese. Entonces esto es útil si no necesita su objeto fuera de su función. Este objeto se colocará en la pila de hilos actual.

Si escribe dentro del cuerpo de la función:

 Object *myObject = new Object;

entonces instancia de clase de Objeto apuntada por myObject no será destruido una vez que la función termine, y la asignación está en el montón.

Ahora bien, si usted es un programador de Java, entonces 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 obtendrá basura recolectada, mientras que en c ++ no se liberará, en algún lugar se debe llamar explícitamente a `eliminar myObject; ' de lo contrario, introducirás pérdidas de memoria.

Desde c ++ 11 puedes usar formas seguras de asignaciones dinámicas: new Object, al almacenar valores en shared_ptr / unique_ptr.

std::shared_ptr<std::string> safe_str = make_shared<std::string>("make_shared");

// since c++14
std::unique_ptr<std::string> safe_str = make_unique<std::string>("make_shared"); 

Además, los objetos a menudo se almacenan en contenedores, como map-s o vector-s, que administrarán automáticamente toda la vida de sus objetos.


18
2018-03-03 12:05



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) Ámbito de aplicación, cuando define un objeto sin un 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", puede acceder a él desde cualquier lugar donde tenga un Puntero a esta memoria hasta que llame "eliminar" en el mismo puntero. 2) Si quiere 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 usa mucha memoria, puede consumirlo (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 uno int).

Aparte de eso, debe comprender que "nuevo" asigna memoria en el montón que debe liberarse en algún momento. Cuando no tiene que usar "nuevo", le sugiero que use una definición de objeto regular "en la pila".


12
2018-03-08 10:48



Bueno, la pregunta principal es ¿Por qué debería usar un puntero en lugar del objeto en sí? Y mi respuesta, usted debe (casi) nunca usar puntero en lugar de 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:

Object *myObject = new Object;

¿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 dinámicamente asignó la memoria (palabra clave utilizada) new), también tienes que liberar memoria manualmente, eso significa que en el código deberías tener:

delete myObject;

Esto llama a destructor y libera memoria, parece fácil, sin embargo, en proyectos grandes puede ser difícil detectar si un hilo ha liberado la memoria o no, pero para ello puede intentarlo punteros compartidos, estos disminuyen ligeramente el rendimiento, pero es mucho más fácil trabajar con ellos.


Y ahora algo de introducción ha terminado y vuelve a la pregunta.

Puede usar punteros en lugar de objetos para obtener un mejor rendimiento mientras transfiere datos entre funciones.

Echa un vistazo, tienes std::string (también es un objeto) y contiene realmente muchos datos, por ejemplo, un gran XML, ahora es necesario analizarlo, pero para eso tienes la función void foo(...) que puede ser declarado de diferentes maneras:

  1. void foo(std::string xml); En este caso, copiará todos los datos de su variable a la pila de funciones, llevará algo de tiempo, por lo que su rendimiento será bajo.
  2. void foo(std::string* xml); En este caso, pasará el puntero al objeto, la misma velocidad que el paso size_t variable, sin embargo esta declaración tiene tendencia a errores, porque puede pasar NULL puntero o puntero no válido. Punteros generalmente utilizados en C porque no tiene referencias.
  3. 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).
  4. void foo(const std::string* xml); Aquí es lo mismo que el segundo, solo el valor del puntero no se puede cambiar.
  5. void foo(const std::string& xml); Aquí es lo mismo que el tercero, pero el valor del objeto no se puede cambiar.

Qué más quiero mencionar, puede usar estas 5 formas de pasar datos sin importar qué forma de asignación haya elegido (con new o regular)


Otra cosa para mencionar, cuando creas objetos en regular forma, asigna memoria en la pila, pero mientras la crea con new usted asigna montón Es mucho más rápido asignar pila, pero es un poco pequeña para grandes conjuntos de datos, por lo que si necesitas un objeto grande deberías usar montón, porque puedes obtener desbordamiento de pila, pero generalmente este problema se resuelve usando Contenedores STL y recuerda std::string también es contenedor, algunos chicos lo olvidaron :)


6
2018-03-03 12:02



Digamos que tienes class A que contienen class BCuando quieres llamar a alguna función de class B fuera de class A simplemente obtendrá un puntero a esta clase y podrá hacer lo que quiera y también cambiará el contexto de class B en tus class A

Pero ten cuidado con el objeto dinámico


5
2018-03-03 12:18