2010-04-16 28 views
6

EDITAR: Sé que en este caso, si fuera una clase real, sería mejor no poner la cadena en el montón. Sin embargo, esto es solo un código de muestra para asegurarme de que entiendo la teoría. El código real va a ser un árbol negro rojo, con todos los nodos almacenados en el montón.Forma correcta de reasignar punteros en C++

Quiero asegurarme de que tengo estas ideas básicas correctas antes de continuar (procedo de un fondo de Java/Python). He estado buscando en la red, pero aún no he encontrado una respuesta concreta a esta pregunta.

Cuando reasigna un puntero a un nuevo objeto, ¿tiene que llamar a eliminar primero en el objeto viejo para evitar una pérdida de memoria? Mi intuición me dice que sí, pero quiero una respuesta concreta antes de seguir adelante.

Por ejemplo, vamos a decir que tenía una clase que almacena un puntero a una cadena

class MyClass 
{ 
private: 
    std::string *str; 

public: 
MyClass (const std::string &_str) 
{ 
    str=new std::string(_str); 
} 

void ChangeString(const std::string &_str) 
{ 
    // I am wondering if this is correct? 
    delete str; 
    str = new std::string(_str) 

    /* 
    * or could you simply do it like: 
    * str = _str; 
    */ 
} 
.... 

En el método changeString, lo que sería correcto?

Creo que me estoy colgando si no usa la nueva palabra clave para la segunda forma, todavía se compilará y ejecutará como esperaba. ¿Esto simplemente sobrescribe los datos a los que apunta este puntero? ¿O hace algo más?

Cualquier consejo sería enormemente appricated: D

+2

Es muy probable que no se deba asignar una cadena con 'new' en primer lugar. ¿Tienes alguna razón específica para eso? – UncleBens

+0

Lea la edición en la parte superior de la página – vimalloc

+0

Una cosa más natural que hacer aquí, sería: '* str = _str;' (no es necesario tirar una cadena y crear otra si solo quiere asignar el contenido) – UncleBens

Respuesta

12

Si debe desasignar la instancia antigua y crear otra, primero debe asegurarse de que la creación del nuevo objeto tiene éxito:

void reset(const std::string& str) 
{ 
    std::string* tmp = new std::string(str); 
    delete m_str; 
    m_str = tmp; 
} 

Si se llama a borrar primero, y luego la creación de uno nuevo arroja una excepción, luego la instancia de clase quedará con un puntero colgante. Por ejemplo, su destructor podría terminar intentando borrar el puntero nuevamente (comportamiento indefinido).

También puede evitar eso al establecer el puntero a NULL en el medio, pero la forma anterior es aún mejor: si el restablecimiento falla, el objeto mantendrá su valor original.


En cuanto a la pregunta en el comentario del código.

*str = _str; 

Esto sería lo correcto. Es una asignación de serie normal.

str = &_str; 

Esto asignaría punteros y sería completamente incorrecto. Perderías la instancia de cadena apuntada anteriormente por str. Lo que es peor, es bastante probable que la secuencia que se pasa a la función no se asigne con new en primer lugar (no debe mezclar punteros a objetos asignados dinámicamente y automáticos). Además, puede que esté almacenando la dirección de un objeto de cadena cuyo tiempo de vida finaliza con la llamada a la función (si la referencia constante está vinculada a una temporal).

+1

+1 para abordar el problema de seguridad de excepción. – Void

+0

Buena información. Gracias :) – vimalloc

0

tres comentarios:

Es necesario un destructor también.

~MyClass() 
{ 
    delete str; 
} 

Usted realmente no necesita utilizar memoria asignada montón en este caso. Se podría hacer lo siguiente:

class MyClass { 
    private: 
     std::string str; 

    public: 
     MyClass (const std::string &_str) { 
      str= _str; 
     } 

     void ChangeString(const std::string &_str) { 
      str = _str; 
}; 

no se puede hacer la versión comentada. Eso sería una pérdida de memoria. Java se encarga de eso porque tiene recolección de basura. C++ no tiene esa característica.

+2

Ese es un comentario. –

+0

Tengo un destructor en la clase real que estoy usando. Se suponía que se trataba de un código de muestra para demostrar mi pregunta. Gracias sin embargo. – vimalloc

+0

@Neil, añadiré algo más :) –

3

¿Por qué crees que necesitas guardar un puntero en una cadena de tu clase? Los punteros a las colecciones de C++, como la cadena, en realidad son muy raramente necesarios. Su clase debe casi seguro que el siguiente aspecto:

class MyClass 
{ 
private: 
    std::string str; 

public: 
MyClass (const std::string & astr) : str(astr) 
{ 
} 

void ChangeString(const std::string & astr) 
{ 
    str = astr; 
} 
.... 
}; 
0

Al cambiar la asignación de un puntero a un objeto nuevo, tienes que llamar a delete en el objeto antiguo primero para evitar una pérdida de memoria? Mi intuición me dice que sí, pero quiero una respuesta concreta antes de seguir adelante.

Sí. Si es un puntero sin formato, primero debe eliminar el objeto viejo.

Existen clases de punteros inteligentes que lo harán por usted cuando asigne un nuevo valor.

1

Sólo la localización de aquí, pero

str = _str; 

no habría compilar (que está tratando de asignar _str, que es el valor de una cadena que se pasa por referencia, a STR, que es la dirección de una cadena) .Si quería hacer eso, usted escribiría:

str = &_str; 

(y que tendría que cambiar, ya sea _str o str para que los partidos constnest).

Pero entonces, como tu intuición te dijo, hubieras filtrado el recuerdo de cualquier objeto de cadena que ya estuviera apuntado por str.

Como se señaló anteriormente, cuando agrega una variable a una clase en C++, debe pensar si la variable es propiedad del objeto o de alguna otra cosa.

Si es propiedad del objeto, entonces probablemente sea mejor almacenarlo como un valor y copiar cosas (pero debe asegurarse de que no haya copias en su espalda).

No es propiedad, entonces puede almacenarlo como un puntero, y no necesariamente necesita copiar cosas todo el tiempo.

Otras personas lo explicarán mejor que yo, porque I no estoy realmente cómodo con él. Lo que termino haciendo una gran cantidad está escribiendo código como este:

class Foo { 

private : 
    Bar & dep_bar_; 
    Baz & dep_baz_; 

    Bing * p_bing_; 

public: 
    Foo(Bar & dep_bar, Baz & dep_baz) : dep_bar_(dep_bar), dep_baz_(dep_baz) { 
     p_bing = new Bing(...); 
    } 

    ~Foo() { 
    delete p_bing; 
    } 

Es decir, si un objeto depende de algo en el 'Java'/sentido 'del COI (los objetos existe en otro lugar, no estás creándolo, y solo quiere llamar al método), almacenaría la dependencia como referencia, usando dep_xxxx.

Si creo el objeto, usaría un puntero, con un prefijo p_.

Esto es solo para hacer que el código sea más "inmediato". No estoy seguro de que ayude

Just my 2c.

Buena suerte con la memoria mgt, tienes razón de que es la parte difícil que viene de Java; no escriba el código hasta que esté cómodo, o pasará horas persiguiendo segaults.

Espero que esto ayude!

+0

La administración de la memoria se centra en la idea de quién es responsable de asignar/desasignar una determinada pieza de memoria. En Java/C#, la desasignación se gestiona mediante el alcance y la semántica del lenguaje. En C/C++, lo manejas. –

1

La regla general en C++ es que para cada objeto creado con "nuevo" debe haber un "eliminar". Asegurándose de que siempre suceda en la parte difícil;) Los programadores modernos de C++ evitan crear memoria en el montón (es decir, con "nuevo") como la plaga y usan objetos de la pila en su lugar. Realmente considere si necesita usar "nuevo" en su código. Rara vez es necesario.

Si vienes de un fondo con lenguajes recolectados y te encuentras realmente necesitando utilizar la memoria de pila, te sugiero que uses los indicadores compartidos de impulso. Se les utilice como esto:

#include <boost/shared_ptr.hpp> 
... 
boost::shared_ptr<MyClass> myPointer = boost::shared_ptr<MyClass>(new MyClass()); 

MiPuntero tiene más o menos la misma semántica del lenguaje como un puntero normal, pero shared_ptr utiliza el recuento de referencias para determinar cuándo eliminar el objeto se hace referencia. Básicamente, hazlo tú mismo, recolección de basura.Los documentos están aquí: http://www.boost.org/doc/libs/1_42_0/libs/smart_ptr/smart_ptr.htm

+0

Los programadores modernos de C++ crean todo con nuevo, pero usan el recuento de referencias para evitar escribir borrado. La mayoría de los programadores de C++ trabajan en sistemas donde necesitas un poco más de memoria que una pila (¡a menos que tengas un sistema con una pila de 4 gb!) –

+0

+1 para señalar modernos C++ usa objetos de pila –

+0

@josh - corrígeme si estoy equivocado , pero hay muchas veces en las que necesitarías usar el montón (en este caso para el árbol negro rojo). Si intentáramos hacer un árbol grande por completo en la pila, ¿no podríamos apilar el desbordamiento? ¿O quizás quiere decir que podríamos crear el objeto de árbol negro rojo en la pila (el usuario final), y todos los nodos podrían aún colocarse en el montón (sin que el usuario final tenga que preocuparse por ello)? – vimalloc

1

Voy a escribir una clase para ti.

class A 
{ 
    Foo * foo; // private by default 
public: 
    A(Foo * foo_): foo(foo_) {} 
    A(): foo(0) {} // in case you need a no-arguments ("default") constructor 
    A(const A &a):foo(new Foo(a.foo)) {} // this is tricky; explanation below 
    A& operator=(const &A a) { foo = new Foo(a.foo); return *this; } 
    void setFoo(Foo * foo_) { delete foo; foo = foo_; } 
    ~A() { delete foo; } 
} 

Para las clases que contienen recursos como este, el constructor de copia, el operador de asignación y el destructor son todos necesarios. La parte engañosa del constructor de copias y el operador de asignación es que debe eliminar cada Foo exactamente una vez. Si el inicializador del constructor de copia hubiera dicho :foo(a.foo), ese particular Foo se eliminaría una vez cuando el objeto que se inicializaba se destruyera y una vez cuando se destruyera el objeto inicializado desde (a).

La clase, la forma en que la he escrito, debe documentarse como propiedad del puntero Foo que se está pasando, porque Foo * f = new Foo(); A a(f); delete f; también provocará una eliminación doble.

Otra forma de hacerlo sería utilizar los punteros inteligentes de Boost (que eran el núcleo de los punteros inteligentes del próximo estándar) y tener boost::shared_ptr<Foo> foo; en lugar de Foo * f; en la definición de clase. En ese caso, el constructor de copia debe ser A(const A &a):foo(a.foo) {}, ya que el puntero inteligente se encargará de eliminar Foo cuando se destruyan todas las copias del puntero compartido que apuntan a él. (También hay problemas aquí, especialmente si mezcla shared_ptr<> s con cualquier otra forma de puntero, pero si se atiene a shared_ptr<>).

Nota: Estoy escribiendo esto sin ejecutar a través de un compilador. Estoy buscando la precisión y el buen estilo (como el uso de inicializadores en constructores). Si alguien encuentra un problema, por favor comente.

+0

'foo = foo_' en setFoo no funciona. –

+0

@Roger: Mi agradecimiento. Cambié eso para que lo que asignó a 'foo' fuera' Foo * '. –