2009-02-21 29 views
10

Entiendo que esto se puede interpretar como una de esas preguntas "¿cuál es su preferencia?", Pero realmente quiero saber por qué elegiría uno de los siguientes métodos sobre el otro.¿Por qué debería/no debería usar el operador "nuevo" para instanciar una clase, y por qué?

Supongamos que tenemos un super complejo de clase , tales como:


class CDoSomthing { 

    public: 
     CDoSomthing::CDoSomthing(char *sUserName, char *sPassword) 
     { 
      //Do somthing... 
     } 

     CDoSomthing::~CDoSomthing() 
     { 
      //Do somthing... 
     } 
}; 

¿Cómo debería declarar una instancia local dentro de una función global?


int main(void) 
{ 
    CDoSomthing *pDoSomthing = new CDoSomthing("UserName", "Password"); 

    //Do somthing... 

    delete pDoSomthing; 
} 

- o -


int main(void) 
{ 
    CDoSomthing DoSomthing("UserName", "Password"); 

    //Do somthing... 

    return 0; 
} 
+11

Guau, este título de pregunta es directamente del departamento del departamento de redundancia. – zweiterlinde

+0

@zweiterlinde - Tiene que estar votando ese comentario – random

+0

"¿quién soy yo, y si es así, cuántos?" me viene a la mente. dicho esto, este extraño lenguaje de programación es muy redundante, ¿no crees? simplemente haciendo un poco de drive-by comentando aquí ... – flow

Respuesta

27

Prefiero variables locales, a menos que necesite tiempo de vida del objeto para extenderse más allá del bloque actual. (Las variables locales son la segunda opción). Es más fácil que preocuparse por la administración de la memoria.

P.S. Si necesita un puntero, porque lo necesita para pasar a otra función, sólo tiene que utilizar el operador de dirección:

SomeFunction(&DoSomthing); 
+0

¿Qué hace la segunda opción? – OscarRyz

+0

@Reyes, sí. Su segundo ejemplo depende de RAII. En este caso, no importa (a menos que llame a main ...), pero para muchos otros casos, el manejo de excepciones es un problema con los punteros, y dependiendo de RAII es más seguro. – strager

+0

Siento lo mismo por no preocuparme por la administración de la memoria, pero soy uno de esos desarrolladores de la "vieja escuela" que solo tiene que ver una coincidencia gratuita() para cada malloc(). Supongo que extiendo eso a mis clases, pero cuesta mucho tipeo extra. – NTDLS

3

La segunda versión se desenrollar la pila si se produce una excepción. El primero no lo hará. No veo mucha diferencia de lo contrario.

+0

¡Muy, muy buen punto! ¡Gracias! – NTDLS

+0

Uno se asigna al montón (a menos que se sobrescriba el operador nuevo) y el otro se asigna a la pila. – strager

+0

@strager - el primero tiene un puntero en la pila y un objeto en el montón, el último es todo pila. –

0

Mark Ransom tiene razón, también necesitará crear instancias con new si va a pasar la variable como parámetro a la función CreateThread-esque.

13

El segundo formulario es el llamado patrón RAII (Inicialización de adquisición de recursos es la inicialización). Tiene muchas ventajas sobre el primero.

Cuando use new, usted tiene que usar delete usted mismo, y garantizar que siempre será eliminado, incluso si se produce una excepción. Debe garantizar todo eso usted mismo.

Si utiliza el segundo formulario, cuando la variable se sale del alcance, siempre se limpia automáticamente. Y si se lanza una excepción, la pila se desenrolla y también se limpia.

Por lo tanto, debería preferir RAII (la segunda opción).

+0

¿El segundo no necesita una variable para trabajar? – OscarRyz

+0

@Oscar: Sí, y esa variable se llama DoSomthing en el ejemplo de la pregunta. –

+0

Entendido, sería: DoSomthing.Method(); ¿derecho? – OscarRyz

22

Hay dos consideraciones principales al declarar una variable en la pila en comparación con el control de vida útil y la administración de recursos.

La asignación en la pila funciona muy bien cuando se tiene un control estricto sobre la vida útil del objeto. Eso significa que no va a pasar un puntero o una referencia de ese objeto al código fuera del alcance de la función local. Esto significa, sin parámetros de salida, sin llamadas COM, sin hilos nuevos. Son muchas las limitaciones, pero el objeto se limpiará correctamente para usted en la salida normal o excepcional del alcance actual (aunque es posible que desee leer las reglas de desenrollado de la pila con destructores virtuales). El mayor inconveniente de la asignación de stack: el stack generalmente está limitado a 4K o 8K, por lo que es posible que quieras tener cuidado con lo que le pongas.

La asignación en el montón, por otro lado, requeriría que limpie la instancia manualmente. Eso también significa que tiene mucha libertad para controlar la vida de la instancia. Debe hacer esto en dos escenarios: a) va a pasar ese objeto fuera del alcance; o b) el objeto es demasiado grande y asignarlo a la pila podría provocar el desbordamiento de la pila.

BTW, un buen compromiso entre estos dos es asignar el objeto en el montón y asignarle un puntero inteligente en la pila. Esto asegura que no está desperdiciando la preciada memoria de la pila, al tiempo que obtiene la limpieza automática en la salida del alcance.

+0

muy buena explicación – MahlerFive

+0

Creo que "la pila generalmente se limita a 4K o 8K" no se aplica tanto en estos días. Acabo de hacer un experimento con Visual C++ 2005 express y descubrí que podía colocar una matriz de caracteres de hasta 0xfbf8c (= 1032076) bytes en la pila de una aplicación hello world. Uno más y el auge de la pila: desbordamiento. De lo contrario, agradable. –

+0

Para Windows, la pila está limitada a 1 MB de forma predeterminada (controlable en el momento de creación de subprocesos) en modo de usuario, 12 kB en modo kernel x86 y 24 kB en modo kernel x64. Linux en x86 tiene una pila de 8 kB o 4 kB en modo kernel dependiendo de las opciones de configuración; tal vez eso es lo que estabas pensando? – bk1e

6

Además de lo que se ha dicho hasta ahora, pero hay consideraciones de rendimiento adicionales que deben tenerse en cuenta, particularmente en aplicaciones de asignación de memoria-intensiva:

  1. Usando new asignará memoria del montón. En el caso de una asignación y desasignación intensa (extremadamente frecuente), pagará un alto precio en:
    • bloqueo: el montón es un recurso compartido por todos los hilos en su proceso. Las operaciones en el montón pueden requerir el bloqueo en el administrador de montón (hecho para usted en la biblioteca de tiempo de ejecución), lo que puede ralentizar significativamente las cosas.
    • fragmentation: montones de fragmentos. Puede ver el tiempo que tarda malloc/new y free/delete para volver a aumentar 10 veces. Esto se complica con el problema de bloqueo anterior, ya que lleva más tiempo administrar un montón fragmentado y más hilos hacen cola esperando el bloqueo de recuperación. (En Windows hay un indicador especial que puede establecer para el administrador del montón para que intente heurísticamente reducir la fragmentación.)
  2. Usando el patrón RAII, la memoria simplemente se saca de la pila. Stack es un recurso por subproceso, no se fragmenta, no hay bloqueo involucrado y puede jugar a su favor en términos de localidad de memoria (es decir, memoria caché en el nivel de la CPU).

Entonces , cuando necesite objetos por un período de tiempo breve (o limitado), definitivamente use el segundo enfoque (variable local, en la pila). Si necesita compartir datos entre hilos, use new/malloc (por un lado, tiene que, en la segunda mano estos objetos son típicamente suficientemente longevos, por lo que usted paga esencialmente 0-cost vis-a-vis el heap manager).

+0

Solo para aclarar, lo escribiste mal. Es RAII. Y no fue mi idea llamarlo así. Es un patrón bien conocido, aparentemente inventado por Bjarn Stroustroup. –

2

La mayor diferencia entre los dos es que el nuevo inicia un puntero al objeto.

Al crear el objeto sin nuevo, el objeto iniciado se almacena en la pila. Si se inicia con new, devuelve un puntero al nuevo objeto que se ha creado en el montón. En realidad, devuelve una dirección de memoria que apunta al nuevo objeto. Cuando esto sucede, necesita administrar la memoria de la variable. Cuando haya terminado de usar la variable, deberá llamar a eliminar para evitar una pérdida de memoria. Sin el nuevo operador, cuando la variable se salga del alcance, la memoria se liberará automáticamente.

Si necesita pasar la variable fuera del alcance actual, usar nuevo es más eficiente. Sin embargo, si necesita hacer una variable temporal, o algo que solo se usará temporalmente, tener el objeto en la pila será mejor, ya que no tiene que preocuparse por la administración de la memoria.