2010-04-30 16 views
7

Actualmente estoy trabajando en un proyecto de C++, donde a menudo aparecen matrices dinámicas. Me preguntaba, ¿cuál podría ser la forma correcta de inicializar una matriz dinámica utilizando el operador nuevo? Un colega mío me dijo que es un no-no usar nuevas dentro del constructor, ya que un constructor es una construcción que no debería ser propensa a errores o no debería fallar en absoluto, respectivamente. Ahora consideremos el siguiente ejemplo: Tenemos dos clases, un estado de clase más o menos complejo y un StateContainer de clase, que debe explicarse por sí mismo.Forma correcta de inicializar la matriz dinámica en C++

class State { 
private: 
unsigned smth; 
public: 
State(); 
State(unsigned s); 
}; 

class StateContainer { 
private: 
unsigned long nStates; 
State *states; 
public: 
StateContainer(); 
StateContainer(unsigned long n); 
virtual ~StateContainer(); 
}; 

StateContainer::StateContainer() { 
nStates = SOME_DEFINE_N_STATES; 
states = new State[nStates]; 
if (!states) { 
    // Error handling 
} 
} 

StateContainer::StateContainer(unsigned long n) { 
nStates = n; 
try { 
    states = new State[nStates] 
} catch (std::bad_alloc &e) { 
    // Error handling 
} 
} 

StateContainer::~StateContainer() { 
if (states) { 
    delete[] states; 
    states = 0; 
} 
} 

Ahora realidad, tengo dos preguntas:

1.) ¿Está bien, para llamar nueva dentro de un constructor, o es mejor para crear un init extra() - Método para el Estado- Matriz y por qué?

2.) ¿Cuál es la mejor manera de comprobar si el nuevo tuvo éxito:

if (!ptr) std::cerr << "new failed." 

o

try { /*new*/ } catch (std::bad_alloc) { /*handling*/ } 

3.) Ok sus tres cuestiones; o) bajo el capó, nuevo hace una especie de

ptr = (Struct *)malloc(N*sizeof(Struct)); 

Y luego llame al constructor, ¿verdad?

+8

Supongo que no está permitido el uso de 'vector'? – GManNickG

+0

Tu colega está equivocado. Es perfectamente aceptable usar 'new' en un constructor. –

Respuesta

7

Debería dejar que se propague std::bad_alloc; de todos modos, no hay nada razonable que pueda hacer.

En primer lugar, lanzar una excepción desde el constructor is the only reliable way to signal a problem - si no hay excepciones, significa que el objeto está completamente construido. Por lo tanto, capturar std::bad_alloc por sí solo no ayudará contra otras posibles excepciones.

Entonces, ¿qué se puede hacer para "manipularlo" de tal manera que el otro código sea consciente y pueda reaccionar adecuadamente?

Utilice excepciones a la derecha - let them propagate to the site where they can be reasonably handled.

0

Tu amigo tiene algo de razón. Sin embargo, es una práctica común hacer reservas de memoria en el Constructor, y desasignaciones en el Destructor.

El problema con los métodos que pueden fallar en los constructores es el siguiente: El constructor no tiene medios tradicionales para notificar a la persona que llama del problema. La persona que llama espera obtener una instancia de objeto que no esté corrupta de ninguna manera.

La solución a este problema es lanzar/propagar una excepción. La excepción siempre se transmitirá y puede ser manejada por la persona que llama.

+0

Tu respuesta es contradictoria; usted dice que no hay una forma tradicional de notificar al usuario de un error y luego describe uno. –

+1

Su amigo está equivocado, con una capital W. Fin de la historia. – jalf

+0

Las excepciones son tan impopulares, la mayoría de C++ solo las usan cuando deben. En su pensamiento "tradicional" no hay lugar para las excepciones y es por eso que no las ven como una forma de notificar al usuario. – ypnos

1

No es una respuesta completa, sólo mis 2 centavos:

1: yo usaría nuevo en el constructor, aunque para matrices dinámicas, STL es el camino a seguir.

2: el manejo normal de errores para nuevo es generar una excepción, por lo que no es útil verificar el puntero devuelto.

3: no olvide el nuevo operador para que la historia sea un poco más interesante.

5

Todo el propósito de un constructor es construir un objeto. Eso incluye la inicialización. Cuando el constructor termina de ejecutarse, el objeto debe estar listo para usar. Eso significa que el constructor debe realizar cualquier inicialización que sea necesaria.

Lo que sugiere su amigo conduce a un código que no se puede mantener y propenso a errores, y va en contra de todas las buenas prácticas de C++. El está equivocado.

En cuanto a sus matrices dinámicas, use std::vector en su lugar. Y para inicializar, basta con pasar un parámetro al constructor:

std::vector<int>(10, 20) 

creará un vector de 10 enteros, todos ellos inicializado con el valor 20.

+0

¿Qué quiere decir con "incluye"? Para mí, * construction * e * initialization * son sinónimos. – fredoverflow

0
  1. Si usted está mirando hacia fuera para un tipo de retorno, es decir, si la función tiene que devolver un estado, entonces use la función separada (init()) para asignar memoria.

    Si marca comprobar si la memoria se asignó comprobando la condición NULL en todas las funciones miembro, entonces asigne memoria en el propio constructor.

  2. El manejo de excepciones (es decir, try ... catch) es una mejor opción.

  3. Además de llamar al constructor, el puntero "this" también se inicializa.

1

Respuesta corta:
No, su amigo está equivocado. El constructor es donde haces asignación + inicialización. Incluso tenemos un término llamado "Adquisición de recursos es inicialización" (RAII) ... las clases adquieren recursos como parte de su inicialización en el constructor y las clases liberan esos recursos adquiridos en sus destructores.

Respuesta larga:

Consideremos el siguiente código en un constructor:

member1 = new whatever1[n]; 
member2 = new whatever2[m]; 

Ahora, supongamos que en lo anterior que la segunda asignación fuera a lanzar una excepción, ya sea debido a la construcción de Whatever2 falló y lanzó una excepción, o la asignación falló y arrojó std :: bad_alloc. El resultado es que la memoria que ha asignado para lo que sea 1 se habrá filtrado.

Por esta razón, es mejor utilizar una clase de contenedor, como std :: vector:

MyClass::MyClass(std::size_t n, std::size_t m) : member1(n), member2(m) {} 
// where member1 and member2 are of type std::vector 

Cuando se utiliza el tipo std :: vector, si falla la segunda asignación, el destructor de la anterior std :: se invocará el vector, lo que hará que los recursos se liberen de manera apropiada. Esto es probablemente lo que quiso decir su amigo cuando dijo que no debería usar nuevo (en su lugar, debería usar una clase de contenedor), en cuyo caso sería en su mayoría correcto ... aunque hay clases de punteros inteligentes como boost :: shared_ptr que le brinda estas mismas garantías de seguridad y donde aún necesitará usar new, por lo que todavía no está del todo bien.

Tenga en cuenta que si tiene un solo objeto/matriz que está asignando, entonces esto no es un problema ... que es la forma en que están las cosas en su código ... no tiene que preocuparse por las fugas debidas a alguna otra asignación fallando. Además, debo agregar que new tendrá éxito o lanzará una excepción; no devolverá NULL, por lo que ese control no tiene sentido. El único caso en el que debe capturar std :: bad_alloc es en el caso en que se le haya obligado a realizar varias asignaciones (está prohibido el uso de std :: vector), en cuyo caso puede desasignar los otros recursos en el controlador, pero luego vuelves a lanzar la excepción, porque debes dejar que se propague.

Cuestiones relacionadas