Miembros de la clase que son objetos-Punteros o no? C++


Si creo una clase MyClass y tiene algún miembro privado que diga MyOtherClass, ¿es mejor hacer MyOtherClass un puntero o no? ¿Qué significa también tenerlo como no un puntero en términos de dónde se almacena en la memoria? ¿Se creará el objeto cuando se cree la clase?

Noté que los ejemplos en QT usualmente declaran miembros de clase como punteros cuando son clases.

Saludos

Mark

Author: rkellerm, 2010-10-06

11 answers

Si creo una clase MyClass y tiene algún miembro privado que diga MyOtherClass, ¿es mejor hacer MyOtherClass un puntero o no?

Generalmente debe declararlo como un valor en su clase. será local, habrá menos posibilidades de errores, menos asignaciones ultimately en última instancia, menos cosas que podrían salir mal, y el compilador siempre puede saber que está allí en un desplazamiento especificado... ayuda a la optimización y la reducción binaria en unos pocos niveles. habrá algunos en los casos en los que sepa que tendrá que lidiar con un puntero (es decir, polimórfico, compartido, requiere reasignación), generalmente es mejor usar un puntero solo cuando sea necesario, especialmente cuando es privado/encapsulado.

¿Qué significa también tenerlo como no un puntero en términos de dónde se almacena en la memoria?

Su dirección será cercana (o igual a) this g gcc (por ejemplo) tiene algunas opciones avanzadas para volcar datos de clase (tamaños, vtables, compensaciones)

¿ Se creará el objeto cuando se cree la clase?

Sí - el tamaño de MyClass crecerá por sizeof (MyOtherClass), o más si el compilador lo realinea (por ejemplo, a su alineación natural)

 28
Author: justin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-06 10:35:07

¿Dónde se almacena su miembro en la memoria?

Echa un vistazo a este ejemplo:

struct Foo { int m; };
struct A {
  Foo foo;
};
struct B {
  Foo *foo;
  B() : foo(new Foo()) { } // ctor: allocate Foo on heap
  ~B() { delete foo; } // dtor: Don't forget this!
};

void bar() {
  A a_stack; // a_stack is on stack
             // a_stack.foo is on stack too
  A* a_heap = new A(); // a_heap is on stack (it's a pointer)
                       // *a_heap (the pointee) is on heap
                       // a_heap->foo is on heap
  B b_stack; // b_stack is on stack
             // b_stack.foo is on stack
             // *b_stack.foo is on heap
  B* b_heap = new B(); // b_heap is on stack
                       // *b_heap is on heap
                       // b_heap->foo is on heap
                       // *(b_heap->foo is on heap
  delete a_heap;
  delete b_heap;
  // B::~B() will delete b_heap->foo!
} 

Definimos dos clases A y B. A almacena un miembro público foo de tipo Foo. B tiene un miembro foo de tipo pointer to Foo.

¿Cuál es la situación para A:

  • Si crea una variable a_stack de tipo A en la pila , entonces el objeto (obviamente) y sus miembros también están en la pila .
  • Si crea un puntero a A como a_heap en el ejemplo anterior, solo la variable puntero está en la pila ; todo lo demás (el objeto y sus miembros) están en la pila .

¿Cómo se ve la situación en el caso de B:

  • se crea Ben la pila : entonces tanto el objeto como su miembro fooestán en la pila , pero el objeto al que fooapunta (el puntito) está en la pila . En resumen: b_stack.foo (el puntero) está en la pila, pero *b_stack.foo el (puntiagudo) está en el montón.
  • se crea un puntero a B llamado b_heap: b_heap (el puntero) está en la pila, *b_heap (el puntero) está en el montón , así como el miembro b_heap->foo y *b_heap->foo.

¿Se creará automáticamente el objeto?

  • En el caso de A: Yes, foo se creará automáticamente llamando al constructor implícito por defecto de Foo. Esto creará un integer pero no inicializarlo (tendrá un número aleatorio)!
  • En el caso de B: Si omites nuestro ctor y dtor, foo (el puntero) también se creará e inicializará con un número aleatorio, lo que significa que apuntará a una ubicación aleatoria en el montón. Pero tenga en cuenta,que el puntero existe! Tenga en cuenta también que el constructor implícito por defecto no asignará algo para foo para usted, tiene que hacer esto explícitamente. Es por eso que generalmente necesita un constructor explícito y un destructor para asignar y eliminar el puntero de su puntero miembro. No se olvide de copy semantics: ¿qué le sucede al puntito si copia el objeto (a través de la construcción de copia o asignación)?

¿Cuál es el punto de todo esto?

Hay varios casos de uso de usar un puntero a un miembro:

  • Para apuntar a un objeto que no posee. Digamos que su clase necesita acceso a una enorme estructura de datos que es muy costosa de copiar. Entonces tú podría guardar un puntero a esta estructura de datos. Tenga en cuenta que en este caso la creación y la eliminación de la estructura de datos está fuera del alcance de su clase. Alguien más tiene que cuidarse.
  • Aumentando el tiempo de compilación, ya que en su archivo de cabecera no tiene que ser definido el pointee.
  • Un poco más avanzado; Cuando su clase tiene un puntero a otra clase que almacena todos los miembros privados, el modismo " Pimpl": http://c2.com/cgi/wiki?PimplIdiom , echa también un vistazo a Sutter, H. (2000): Excepcional C++, p. 99--119
  • Y algunos otros, mira las otras respuestas{[45]]}

Consejo

Tenga mucho cuidado si sus miembros son punteros y los posee. Tienes que escribir constructores adecuados, destructores y pensar en constructores de copia y operadores de asignación. ¿Qué sucede con el puntito si copia el objeto? Por lo general, tendrá que copiar construir el ¡puntiagudo también!

 19
Author: WolfgangP,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-06 11:43:05

En C++, los punteros son objetos por derecho propio. No están realmente atados a lo que apunten, y no hay una interacción especial entre un puntero y su puntiagudo (¿es esa una palabra?)

Si crea un puntero, crea un puntero y nada más. No creas el objeto al que podría o no apuntar. Y cuando un puntero sale del alcance, el objeto apuntado no se ve afectado. Un puntero no afecta de ninguna manera la vida útil de lo que apunta a.

Así que en general, debes no usar punteros por defecto. Si su clase contiene otro objeto, ese otro objeto no debería ser un puntero.

Sin embargo, si su clase conoce otro objeto, entonces un puntero podría ser una buena manera de representarlo (ya que múltiples instancias de su clase pueden apuntar a la misma instancia, sin tomar posesión de ella, y sin controlar su vida útil)

 17
Author: jalf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-06 10:19:23

La sabiduría común en C++ es evitar el uso de punteros (desnudos) tanto como sea posible. Especialmente punteros desnudos que apuntan a la memoria asignada dinámicamente.

La razón es porque los punteros hacen que sea más difícil escribir clases robustas, especialmente cuando también tiene que considerar la posibilidad de que se produzcan excepciones.

 5
Author: Bart van Ingen Schenau,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-06 10:23:55

Esta pregunta podría ser debatida sin fin, pero los fundamentos son:

Si Miotraclase no es un puntero:

  • La creación y destrucción de MyOtherClass es automática, lo que puede reducir los errores.
  • La memoria utilizada por MyOtherClass es local a la MyClassInstance, lo que podría mejorar el rendimiento.

Si Miotraclase es un puntero:

  • La creación y destrucción de Miotraclase es su responsabilidad
  • Miotraclase puede ser NULL, lo que podría tener significado en su contexto y podría ahorrar memoria
  • Dos instancias de MyClass podrían compartir la misma mioterclase
 3
Author: Drew Dormann,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-06 10:24:34

Sigo la siguiente regla: si el objeto miembro vive y muere con el objeto encapsulado, no utilice punteros. Necesitará un puntero si el objeto miembro tiene que sobrevivir al objeto encapsulado por alguna razón. Depende de la tarea en cuestión.

Normalmente se usa un puntero si el objeto miembro se le da y no se crea por usted. Entonces normalmente no tienes que destruirlo tampoco.

 3
Author: chvor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-06 10:29:32

Algunas ventajas del miembro puntero:

  • El objeto hijo (MyOtherClass) puede tener una vida útil diferente a la de su padre (MyClass).
  • El objeto puede ser compartido entre varios objetos MyClass (u otros).
  • Al compilar el archivo de cabecera para MyClass, el compilador no necesariamente tiene que conocer la definición de MyOtherClass. No tiene que incluir su encabezado, disminuyendo así los tiempos de compilación.
  • Hace que el tamaño de MyClass sea más pequeño. Esto puede ser importante para rendimiento si su código realiza una gran cantidad de copias de objetos MyClass. Simplemente puede copiar el puntero de MyOtherClass e implementar algún tipo de sistema de conteo de referencia.

Ventajas de tener el miembro como objeto:

  • No es necesario escribir código explícitamente para crear y destruir el objeto. Es más fácil y menos propenso a errores.
  • Hace que la gestión de memoria sea más eficiente porque solo se necesita asignar un bloque de memoria en lugar de dos.
  • Implementar operadores de asignación, copiar/mover constructores, etc. es mucho más simple.
  • Más intuitivo
 3
Author: Timo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-06 11:01:39

Si haces el objeto MyOtherClass como miembro de tu MyClass:

size of MyClass = size of MyClass + size of MyOtherClass

Si haces el objeto MyOtherClass como miembro puntero de tu MyClass:

size of MyClass = size of MyClass + size of any pointer on your system

Es posible que desee mantener MyOtherClass como un miembro puntero porque le da la flexibilidad de apuntar a cualquier otra clase que se derive de ella. Básicamente le ayuda a implementar el polimorfismo dynamice.

 1
Author: Alok Save,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-06 10:23:56

Depende... :-)

Si usa punteros para decir a class A, debe crear el objeto de tipo A, por ejemplo, en el constructor de su clase

 m_pA = new A();

Además, no olvides destruir el objeto en el destructor o tienes una fuga de memoria:

delete m_pA; 
m_pA = NULL;

En cambio, tener un objeto de tipo A agregado en su clase es más fácil, no puede olvidarse de destruirlo, porque esto se hace automáticamente al final de la vida útil de su objeto.

Por otra parte, tener un pointer tiene las siguientes ventajas:

  • Si su objeto está asignado en el pila y tipo A utiliza una gran cantidad de memoria esto no se asignará desde el apilar pero de la pila.

  • Puede construir su objeto A más tarde (por ejemplo, en un método Create) o destruirlo antes (en método Close)

 1
Author: ur.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-06 10:29:53

Una ventaja de la clase padre que mantiene la relación con un objeto miembro como un puntero (std::auto_ptr) al objeto miembro es que puede reenviar declarar el objeto en lugar de tener que incluir el archivo de cabecera del objeto.

Esto desacopla las clases en el tiempo de compilación, lo que permite modificar la clase de encabezado del objeto miembro sin causar que todos los clientes de su clase padre también se recompilen, aunque probablemente no accedan a la clase del objeto miembro función.

Cuando usa un auto_ptr, solo necesita encargarse de la construcción, lo que normalmente podría hacer en la lista inicializador. La destrucción junto con el objeto padre está garantizada por el auto_ptr.

 1
Author: andreas buykx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-06 11:26:46

Lo más sencillo es declarar a tus miembros como objetos. De esta manera, no tiene que preocuparse por la construcción, destrucción y asignación de copias. Todo esto se soluciona automáticamente.

Sin embargo, todavía hay algunos casos en los que desea punteros. Después de todo, los lenguajes administrados (como C# o Java) en realidad contienen objetos miembros por punteros.

El caso más obvio es cuando el objeto a mantener es polimórfico. En Qt, como usted señaló, la mayoría de los objetos pertenecen a un enorme la jerarquía de las clases polimórficas, y mantenerlas por punteros es obligatoria ya que no sabe de antemano qué tamaño tendrá el objeto miembro.

Tenga cuidado con algunos errores comunes en este caso, especialmente cuando se trata de clases genéricas. La seguridad de las excepciones es una gran preocupación:

struct Foo
{
    Foo() 
    {
        bar_ = new Bar();
        baz_ = new Baz(); // If this line throw, bar_ is never reclaimed
                          // See copy constructor for a workaround
    }

    Foo(Foo const& x)
    {
        bar_ = x.bar_.clone();
        try { baz_ = x.baz_.clone(); }
        catch (...) { delete bar_; throw; }
    }

    // Copy and swap idiom is perfect for this.
    // It yields exception safe operator= if the copy constructor
    // is exception safe.
    void swap(Foo& x) throw()
    { std::swap(bar_, x.bar_); std::swap(baz_, x.baz_); }

    Foo& operator=(Foo x) { x.swap(*this); return *this; }

private:
    Bar* bar_;
    Baz* baz_;
};

Como puede ver, es bastante engorroso tener constructores seguros de excepciones en presencia de punteros. Usted debe mirar RAII y punteros inteligentes (hay un montón de recursos y en otro lugar de la web).

 0
Author: Alexandre C.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-06 11:58:58