c++ punteros ejercicios - ¿Por qué debería usar un puntero en lugar del objeto en sí?





11 Answers

Hay muchos casos de uso para los punteros.

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

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

Referencia semántica y evitando copiar . Para tipos no polimórficos, un puntero (o una referencia) evitará copiar un objeto potencialmente costoso

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 semánticas de movimiento que pueden evitar muchas copias de objetos caros en el argumento de la 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 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<> o std::shared_ptr<> ). Considerar:

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

contra

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

Un puntero en bruto solo debe usarse como una "vista" y no debe implicar de ninguna manera a la propiedad, ya sea a través de la creación directa o implícitamente a través de los valores de retorno. Consulte también estas preguntas y respuestas 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 vivo. Los objetos normales (no creados por new , ya sea directamente por usted o dentro de una clase de recurso) se destruyen cuando se salen del alcance.

resueltos pdf declaracion

Vengo de un fondo de Java y he empezado 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 propios objetos, 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




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.




C ++ le ofrece tres formas de pasar un objeto: por puntero, por referencia y por valor. Java te limita con la última (la única excepción son los tipos primitivos como int, boolean, etc.). Si quieres usar C ++ no solo como un juguete raro, entonces es mejor que conozcas 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, grande y horrible. Sin embargo, no puede proporcionar una protección del 100% contra las 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 su SUV, más largo será su 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 en forma de 'disparar y olvidar' como GC: siempre se debe tener en cuenta el manejo de la memoria. No solo debe preocuparse por destruir un objeto, sino que también debe evitar destruir el mismo objeto más de una vez.

¿No tienes miedo todavía? Ok: referencias cíclicas - manéjalos usted mismo, humano. Y recuerda: mata cada objeto precisamente 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, volvamos a tu pregunta.

Cuando transfiere 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 base de datos: es lo suficientemente inteligente como para evitarlo, no lo es). t usted?) cada vez que haces '='. Y para acceder a los miembros del objeto, usas '.' (punto).

Cuando pasa su objeto por el puntero, copia solo 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 todos, utiliza este elegante operador '->' cuando accede 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 se accede a los miembros a través de '.'.

Y, para volar tu mente una vez más: cuando declaras varias variables separadas por comas, entonces (mira las manos):

  • El tipo es dado a todos.
  • Valor / puntero / modificador de 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



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 vuelva. Así que esto es útil si no necesitas tu objeto fuera de tu función. Este objeto se pondrá en la pila de hilos actual.

Si escribes dentro del cuerpo de la función:

 Object *myObject = new Object;

entonces, la instancia de la clase de objetos señalada por myObject no se destruirá una vez que la función finalice, y la asignación esté en el montón.

Ahora, si usted es programador de Java, el segundo ejemplo es más cercano a 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 en java myObject se recolectará la basura, mientras que en c ++ no se liberará, en algún lugar debe llamar explícitamente `delete myObject; ' De lo contrario introducirás fugas de memoria.

Desde c ++ 11 puede usar formas seguras de asignaciones dinámicas: new Object , almacenando 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 se almacenan a menudo en contenedores, como map-s o vector-s, que administrarán automáticamente la vida útil de sus objetos.




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 references , 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 Objecttipo, asigna memoria para ajustarse a un objeto y llama al constructor predeterminado, suena bien, ¿verdad? Pero en realidad no es tan bueno, si asignó dinámicamente memoria (palabra clave utilizada new), también tiene que liberar memoria manualmente, eso significa que en el código debería tener:

delete myObject;

Esto llama a destructor y libera memoria, parece fácil, sin embargo, en grandes proyectos puede ser difícil detectar si un hilo libera memoria o no, pero para eso puede probar punteros compartidos , esto reduce ligeramente el rendimiento, pero es mucho más fácil trabajar con ellos. ellos.

Y ahora, una introducción ha terminado y vuelve a la pregunta.

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

Eche un vistazo, tiene std::string(también es un objeto) y contiene mucha información, por ejemplo, un gran XML, ahora necesita analizarlo, pero para eso tiene una función void foo(...)que se puede declarar de diferentes maneras:

  1. void foo(std::string xml); En este caso, copiará todos los datos de su variable a la pila de funciones, esto llevará algún 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 la size_tvariable que pasa , sin embargo, esta declaración es propensa a errores, ya que puede pasar el NULLpuntero o el puntero no válido. Los punteros usualmente se usan Cporque no tiene referencias.
  3. void foo(std::string& xml); Aquí se pasa la referencia, básicamente es lo mismo que pasar el puntero, pero el compilador hace algunas cosas y no se puede pasar la referencia no válida (en realidad es posible crear una situación con una referencia no válida, pero es un compilador engañoso).
  4. void foo(const std::string* xml); Aquí es lo mismo que segundo, solo el valor del puntero no se puede cambiar.
  5. void foo(const std::string& xml); Aquí es lo mismo que tercero, pero el valor del objeto no se puede cambiar.

Además, quiero mencionar que puede utilizar estas 5 formas de pasar datos sin importar la asignación que haya elegido (con newo normal ).

Otra cosa a mencionar, cuando creas un objeto de manera regular , asignas memoria en la pila, pero mientras lo creas con newtu asignación de montón. Es mucho más rápido para asignar pila, pero se deja ver una pequeña para realmente grandes conjuntos de datos, por lo que si usted necesita gran objeto que se debe utilizar montón, ya que puede contraer desbordamiento de pila, pero por lo general se resuelve este problema utilizando contenedores STL y recordar std::stringTambién es contenedor, algunos chicos lo olvidaron :)




Hay muchos beneficios de usar punteros para objetar -

  1. Eficiencia (como ya has señalado). Pasar objetos a funciones significa crear nuevas copias de objetos.
  2. Trabajar con objetos de bibliotecas de terceros. Si su objeto pertenece a un código de un tercero y los autores pretenden utilizar sus objetos solo a través de los punteros (no hay constructores de copia, etc.), la única manera de pasar este objeto es usar punteros. Pasar por valor puede causar problemas. (Copia profunda / problemas de copia superficial).
  3. Si el objeto es propietario de un recurso y desea que la propiedad no debe ser sahred con otros objetos.



Object *myObject = new Object;

Al hacer esto se creará una referencia a un Objeto (en el montón) que debe eliminarse explícitamente para evitar la pérdida de memoria .

Object myObject;

Al hacer esto, se creará un objeto (myObject) del tipo automático (en la pila) que se eliminará automáticamente cuando el objeto (myObject) quede fuera del alcance.




Usted no debería . La gente (mucha gente, tristemente) lo escribe por ignorancia.

A veces, la asignación dinámica tiene su lugar pero, en los ejemplos que da, está mal .

Si quiere pensar en la eficiencia, esto es peor , porque no introduce una buena dirección indirecta. Este tipo de programación es más lenta y más propensa a errores .




Una razón para usar punteros es para interactuar con las funciones C. Otra razón es ahorrar memoria; por ejemplo: en lugar de pasar un objeto que contiene una gran cantidad de datos y tiene un constructor de copias con un uso intensivo del procesador a una función, simplemente pase un puntero al objeto, ahorrando memoria y velocidad, especialmente si está en un bucle, sin embargo la referencia sería mejor en ese caso, a menos que esté utilizando una matriz de estilo C.




Incluiré un caso de uso importante de puntero. Cuando estás almacenando algún objeto en la clase base, pero podría ser polimórfico.

Class Base1 {
};

Class Derived1 : public Base1 {
};


Class Base2 {
  Base *bObj;
  virtual void createMemerObects() = 0;
};

Class Derived2 {
  virtual void createMemerObects() {
    bObj = new Derived1();
  }
};

Entonces, en este caso, no puede declarar bObj como un objeto directo, debe tener un puntero.




Ya hay muchas respuestas excelentes, pero déjame darte un ejemplo:

Tengo una clase de artículo simple:

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

Hago un vector para contener un montón de ellos.

std::vector<Item> inventory;

Creo un millón de objetos de Objeto y los empujo de nuevo sobre el vector. Ordeno el vector por nombre, y luego hago una búsqueda binaria iterativa simple para un nombre de elemento particular. Pruebo el programa, y ​​tarda más de 8 minutos en terminar de ejecutarse. Entonces cambio mi vector de inventario de esta manera:

std::vector<Item *> inventory;

... y crear mis objetos de millones de artículos a través de nuevos. Los ÚNICOS cambios que hago en mi código son para usar los punteros a los Elementos, a excepción de un bucle 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 cual sigue siendo considerable.




Related