[c++] ¿Por qué debería usar un puntero en lugar del objeto en sí?


10 Answers

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 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<> 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 devueltos. Ver también este Q & A de las preguntas frecuentes de C ++ .

Control de tiempo de vida más preciso Cada vez que se copia un puntero compartido (por ejemplo, como un argumento de función), el recurso al que apunta se mantiene activo. Los objetos normales (no creados por uno new , ya sea directamente o dentro de una clase de recurso) se destruyen cuando salen del alcance.

Question

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




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

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

Object myObject;

dentro de la función, su myObject se destruirá una vez que esta función regrese. Entonces esto es útil si no necesitas tu objeto fuera de tu 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;

luego, la instancia de la clase Object apuntada por myObject no se destruirá una vez que la función finalice, y la asignación estará en heap.

Ahora bien, si usted es 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 puede 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 la vida útil de sus objetos.




A pointer directly references the memory location of an object. Java has nothing like this. Java has references that reference the location of object through hash tables. You cannot do anything like pointer arithmetic in Java with these references.

To answer your question, it's just your preference. I prefer using the Java-like syntax.




Well the main question is Why should I use a pointer rather than the object itself? And my answer, you should (almost) never use pointer instead of object, because C++ has references , it is safer then pointers and guarantees the same performance as pointers.

Another thing you mentioned in your question:

Object *myObject = new Object;

¿Como funciona? It creates pointer of Object type, allocates memory to fit one object and calls default constructor, sounds good, right? But actually it isn't so good, if you dynamically allocated memory (used keyword new ), you also have to free memory manually, that means in code you should have:

delete myObject;

This calls destructor and frees memory, looks easy, however in big projects may be difficult to detect if one thread freed memory or not, but for that purpose you can try shared pointers , these slightly decreases performance, but it is much easier to work with them.

And now some introduction is over and go back to question.

You can use pointers instead of objects to get better performance while transferring data between function.

Take a look, you have std::string (it is also object) and it contains really much data, for example big XML, now you need to parse it, but for that you have function void foo(...) which can be declarated in different ways:

  1. void foo(std::string xml); In this case you will copy all data from your variable to function stack, it takes some time, so your performance will be low.
  2. void foo(std::string* xml); In this case you will pass pointer to object, same speed as passing size_t variable, however this declaration has error prone, because you can pass NULL pointer or invalid pointer. Pointers usually used in C because it doesn't have references.
  3. void foo(std::string& xml); Here you pass reference, basically it is the same as passing pointer, but compiler does some stuff and you cannot pass invalid reference (actually it is possible to create situation with invalid reference, but it is tricking compiler).
  4. void foo(const std::string* xml); Here is the same as second, just pointer value cannot be changed.
  5. void foo(const std::string& xml); Here is the same as third, but object value cannot be changed.

What more I want to mention, you can use these 5 ways to pass data no matter which allocation way you have chosen (with new or regular ).

Another thing to mention, when you create object in regular way, you allocate memory in stack, but while you create it with new you allocate heap. It is much faster to allocate stack, but it is kind a small for really big arrays of data, so if you need big object you should use heap, because you may get , but usually this issue is solved using STL containers and remember std::string is also container, some guys forgot it :)




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 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 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 (familia de punteros inteligentes , QObject en Qt, etc.), pero ninguno de ellos se puede usar como "disparar y olvidar" como GC: siempre debes tener 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 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



There are many excellent answers already, but let me give you one example:

I have an simple Item class:

 class Item
    {
    public: 
      std::string name;
      int weight;
      int price;
    };

I make a vector to hold a bunch of them.

std::vector<Item> inventory;

I create one million Item objects, and push them back onto the vector. I sort the vector by name, and then do a simple iterative binary search for a particular item name. I test the program, and it takes over 8 minutes to finish executing. Then I change my inventory vector like so:

std::vector<Item *> inventory;

...and create my million Item objects via new. The ONLY changes I make to my code are to use the pointers to Items, excepting a loop I add for memory cleanup at the end. That program runs in under 40 seconds, or better than a 10x speed increase. EDIT: The code is at http://pastebin.com/DK24SPeW With compiler optimizations it shows only a 3.4x increase on the machine I just tested it on, which is still considerable.




Let's say that you have class A that contain class B When you want to call some function of class B outside class A you will simply obtain a pointer to this class and you can do whatever you want and it will also change context of class B in your class A

But be careful with dynamic object




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




With pointers ,

  • can directly talk to the memory.

  • can prevent lot of memory leaks of a program by manipulating pointers.




Object *myObject = new Object;

Doing this will create a reference to an Object (on the heap) which has to be deleted explicitly to avoid memory leak .

Object myObject;

Doing this will create an object(myObject) of the automatic type (on the stack) that will be automatically deleted when the object(myObject) goes out of scope.




In areas where memory utilization is at its premium , pointers comes handy. For example consider a minimax algorithm, where thousands of nodes will be generated using recursive routine, and later use them to evaluate the next best move in game, ability to deallocate or reset (as in smart pointers) significantly reduces memory consumption. Whereas the non-pointer variable continues to occupy space till it's recursive call returns a value.




Related