2010-03-16 16 views

Respuesta

10

Porque la especificación del lenguaje no lo permite. Justo como es el lenguaje. Muy molesto si estás acostumbrado a Java u otros lenguajes que lo permiten. Sin embargo, te acostumbras después de un tiempo. Todos los idiomas tienen sus peculiaridades, este es solo uno de C++. Estoy seguro de que los escritores de las especificaciones tienen sus razones.

La mejor forma de solucionar esto que he encontrado es hacer una función de inicialización común y hacer que ambos constructores lo llamen.

Algo como esto:

class Foo { 
public: 
    Foo() {initialize(1);} 
    Foo(int nX) { initialize(nx); } 

private: 
    void initialize(int nx) { x=nx; } 
    int x; 
}; 
+0

Este es el método preferido –

+2

Se puede preferir pero es ineficaz ya que todos los miembros se construyen primero por defecto, luego se sobrescriben mediante la inicialización. –

+2

Bueno, eso se convierte en una cuestión de optimización frente a minimizar la duplicación de código. Depende de qué es más importante para el programador que escribe. –

2

No puedes hacer esto. Ver la sección 10.3: http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.3. Puede intentar hacerlo pero al hacerlo construirá un nuevo objeto temporal (no esto) y se destruirá una vez que el control se mueva.

Lo que puede hacer, sin embargo, es crear una función privada que inicialice variables, una que su constructor predeterminado o un constructor con parámetros puedan llamar.

4

Es una opción de diseño del lenguaje.

Un constructor es una operación de una vez (por objeto) que crea un nuevo objeto en la memoria no inicializada. Solo se puede llamar a un constructor para un objeto, una vez que ha finalizado, comienza la vida del objeto y no se puede llamar ni reanudar ningún otro constructor en ese objeto.

En el otro extremo de su vida, un destructor solo puede (válidamente) llamarse una vez por objeto y tan pronto como se ingresa el destructor, la vida útil del objeto termina.

Una razón prinicipal para esto es hacer explícita cuando se llevará a cabo un destructor de objetos y en qué estado se puede esperar que el objeto de estar en.

Si un constructor de la clase completa satisfactoriamente entonces se le puede llamar destructor, de lo contrario la vida del objeto nunca ha comenzado y no se llamará al destructor. Esta garantía puede ser importante cuando un objeto adquiere recursos en su constructor que necesitan ser liberados en su destructor. Si la adquisición del recurso falla, generalmente se hará que el constructor falle; si el destructor se ejecutó de todos modos, podría intentar liberar un recurso que nunca se había adquirido con éxito.

Si permite que los constructores se llamen entre sí, puede no quedar claro si una llamada o un constructor llamado es responsable del recurso. Por ejemplo, si el constructor que realiza la llamada falla después de que el constructor llamado retorna, ¿debería ejecutarse el destructor? El constructor llamado puede haber adquirido algo que necesita ser liberado o tal vez eso fue lo que causó que fallara el construtor llamante y no se debe llamar al destructor porque el identificador del recurso nunca fue válido.

Para simplificar las reglas de destrucción, es más simple si cada objeto es creado por un único constructor y, si se crea correctamente, destruido por un solo destructor.

Tenga en cuenta que en C++ 11 un constructor podrá delegar en un constructor diferente, pero existen limitaciones que realmente no relajan el principal de una construcción por objeto. (El constructor principal puede reenviar a un constructor de destino, pero si lo hace no debe nombrar a nada más (clases base o miembros) en su lista de inicializadores.Estos serán inicializados por el constructor de destino, una vez que el constructor de destino devuelve el cuerpo del constructor principal se completará (inicialización adicional). No es posible reconstruir ninguna base o miembros, aunque le permite compartir código de constructor entre constructores.)

+0

Bueno, esto explica la vista, pero no realmente por qué los redactores de las especificaciones eligen verlo de esa manera. ¿Hay realmente alguna razón por la cual los constructores no pueden delegarse entre sí? ¿Por qué el constructor tiene que ser el punto de la creación del objeto * frente simplemente al punto de inicialización del objeto que se llama poco después de la creación? –

+1

@dbingham: TTBOMK, C++ 11 permitirá el reenvío a otros constructores. – sbi

+0

@dbingham: Le permite un control máximo de la construcción inicial de los subelementos base y miembro, en muchos idiomas que le permiten llamar entre constructores, el constructor es realmente una rutina de inicialización posterior a la construcción, que puede ser útil pero borra el línea entre cuando un objeto ha terminado la construcción y su vida útil realmente ha comenzado y sus invariantes están en su lugar. Esta claridad puede ser bastante importante al inicializar los miembros de const. –

2

Hay un truco realmente horrible que he visto utilizar para llamar a otro ctor. Utiliza la nueva operación de colocación en este puntero. ERK

De esta manera:

Class::Class(int x) : x_(x) {} 
Class::Class() { 
    new (this) Class(0); 
} 
Class::Class(const Class &other) : x_(other.x_) {} 
Class& operator=(const Class &other) { 
    new (this) Class(other); 
    return *this; 
} 

Tenga en cuenta que no estoy recomendando esto, y yo no sé qué terribles efectos que podría tener en C++ estructuras como clases base virtuales, etc. pero espero que hay algunos

+1

Desafortunadamente, este método comparte todas las desventajas de la rutina 'initialize' ya que los miembros * todavía * serán construidos por defecto antes de que ocurra la colocación-new. Agregue a eso todos los problemas potenciales que citó ... –

+0

No hay problema con las bases virtuales, pero esto es extremadamente peligroso: no puede hacer eso si hay 'const' o miembros de referencia (o miembros de miembros, miembros de clases base, etc.). Romperá las clases derivadas si lo hace en una función miembro (no es un problema en el controlador). – curiousguy

+0

Además, si el otro constructor arroja una excepción, en una función miembro como el operador de asignación, usted tiene un gran lío que no siempre puede arreglar, ya que su interlocutor espera que exista un objeto y sea destructible. Entonces necesita manejar la excepción y tener un constructor de nohrow. Es aún más complicado en un constructor. – curiousguy

0

Aunque según los estándares, los proveedores son libres de implementar el enlace de datos a su manera, si consideramos la implementación más popular: este puntero, podemos ver una razón por la cual esto no se puede implementar.

Suponga que tiene una clase:

class A 
{ 
    public: 
    A(){} 
    A(int a){} 
} ; 

Con la implementación de este puntero, esta clase sería algo como:

class A 
{ 
    public: 
    A(A *this){} 
    A(A *this,int a){} 
} ; 

Normalmente, usted podría crear objetos de esta manera:

A ob ; 

Ahora cuando el compilador ve esto, asigna memoria para esto y pasa la dirección de este bloque de memoria asignada al constructor de A que construye el objeto. Intentaría hacer lo mismo cada vez para cada constructor llamado.

Ahora cuando intenta llamar a un constructor dentro de otro constructor, en lugar de asignar nueva memoria, el compilador debe pasar los objetos actuales a este. ¡De ahí la inconsistencia!

Luego, otra razón que veo es que, aunque es posible que desee llamar a un constructor dentro de otro, aún desea que un constructor llame a los constructores por defecto para todos los objetos dentro de la clase. Ahora, si un constructor llamara a otro, la construcción predeterminada debería ocurrir para el primer constructor y no para el siguiente. Implementar este comportamiento significa que habría varias permutaciones que deben ser manejadas. De lo contrario, se degradaría el rendimiento ya que cada constructor construiría por defecto todos los objetos incluidos.

Esto es lo que puedo pensar como posibles razones para este comportamiento y no tengo ningún estándar que admitir.

+0

Probablemente te va demasiado bajo nivel con tu análisis. ¡En C++ 0x, de repente, esto no va a ser un problema! Depende de la especificación del idioma establecer las reglas del juego. – UncleBens

Cuestiones relacionadas