2009-05-06 17 views
19

Sé que podemos llamar explícitamente al constructor de una clase en C++ utilizando el operador de resolución de ámbito, es decir, className::className(). Me preguntaba dónde exactamente necesitaría hacer tal llamada.Por qué llamar explícitamente a un constructor en C++

+5

No es correcto decir que puede llamar al constructor directamente. El estándar tiene explícitamente (12.1/1): "Los constructores no tienen nombres". Solo puede llamar al constructor a través de otras construcciones, como un molde de estilo de función o una ubicación nueva. –

Respuesta

13

mayoría de las veces, en un constructor de la clase infantil que requieren algunos parámetros:

class BaseClass 
{ 
public: 
    BaseClass(const std::string& name) : m_name(name) { } 

    const std::string& getName() const { return m_name; } 

private: 

    const std::string m_name; 

//... 

}; 


class DerivedClass : public BaseClass 
{ 
public: 

    DerivedClass(const std::string& name) : BaseClass(name) { } 

// ... 
}; 

class TestClass : 
{ 
public: 
    TestClass(int testValue); //... 
}; 

class UniqueTestClass 
    : public BaseClass 
    , public TestClass 
{ 
public: 
    UniqueTestClass() 
     : BaseClass("UniqueTest") 
     , TestClass(42) 
    { } 

// ... 
}; 

... por ejemplo.

Aparte de eso, no veo la utilidad. Solo llamé al constructor en otro código cuando era demasiado pequeño para saber lo que realmente estaba haciendo ...

+4

C++ llama implícitamente al constructor de las clases principales cuando se construye una instancia de una clase derivada, pero llama al constructor predeterminado, a menos que explícitamente invoque un constructor de clases padre específico en la lista de inicializadores. –

+0

Sí, en mi ejemplo, me aseguré de que el único constructor válido para BaseClass requiriera algunos parámetros. No recuerdo un caso que requiera una llamada de constructor predeterminada explícita. Tal vez en herencia virtual? – Klaim

0

No creo que normalmente usarías eso para el constructor, al menos no de la manera que estás describiendo. Sin embargo, lo necesitarías si tienes dos clases en espacios de nombres diferentes. Por ejemplo, para especificar la diferencia entre estas dos clases inventadas, Xml::Element y Chemistry::Element.

Normalmente, el nombre de la clase se utiliza con el operador de resolución de alcance para llamar a una función en el elemento primario heredado de una clase. Entonces, si tiene un perro de clase que hereda de Animal, y ambas clases definen la función Eat() de manera diferente, puede haber un caso en el que desee usar la versión Animal de comer en un objeto Dog llamado "someDog". Mi sintaxis de C++ está un poco oxidada, pero creo que en ese caso dirías someDog.Animal::Eat().

42

También a veces se usa explícitamente un constructor para crear un temporal. Por ejemplo, si tiene alguna clase con un constructor:

class Foo 
{ 
    Foo(char* c, int i); 
}; 

y una función

void Bar(Foo foo); 

pero no tienen un Foo alrededor, que podría hacer

Bar(Foo("hello", 5)); 

Este es como un yeso. De hecho, si tiene un constructor que toma solo un parámetro, el compilador C++ usará ese constructor para realizar moldes implícitos.

Es no legal para llamar a un constructor en un objeto ya existente. Es decir, no puede hacer

Foo foo; 
foo.Foo(); // compile error! 

haga lo que haga. Pero puede invocar un constructor sin asignar memoria; para eso es ubicación nueva.

char buffer[sizeof(Foo)];  // a bit of memory 
Foo* foo = new(buffer) Foo(); // construct a Foo inside buffer 

Da nueva memoria y construye el objeto en ese lugar en lugar de asignar nueva memoria. Este uso se considera malo, y es raro en la mayoría de los tipos de código, pero es común en los códigos incrustados y de estructura de datos. Por ejemplo, std::vector::push_back usa esta técnica para invocar el constructor de copia. De esta forma, solo necesita hacer una copia, en lugar de crear un objeto vacío y usar el operador de asignación.

+4

+1 para la colocación nueva. Es extraño, pero puede ser útil si sabes lo que estás haciendo. –

+0

"De hecho, si tiene un constructor que toma solo un parámetro, el compilador de C++ usará ese constructor para realizar conversiones implícitas". - por lo que muchas personas colocan la palabra clave explícita en constructores de una sola arg por defecto, y solo la quitan si están seguros de que quieren implict casting del tipo de parámetro al tipo de clase. –

+1

-1 No estás llamando al constructor. La sintaxis es ' (lista ctor-arg)', y ser pedante no es lo mismo que ' :: (lista ctor-arg)'. La sintaxis solo hace que parezca que estás llamando al constructor. De hecho, nunca "llamas" a un constructor como una función. –

3

Creo que el mensaje de error de error del compilador C2585 da la mejor razón por la que tendría que utilizar realmente el operador alcance resolución en el constructor, y lo hace con la respuesta de Charlie:

conversión de una clase o tipo de estructura basada en herencia múltiple. Si el tipo hereda la misma clase base más de una vez, la función de conversión u operador debe usar la resolución del ámbito (: :) para especificar cuál de las clases heredadas usar en la conversión.

Imagine que tiene BaseClass, y BaseClassA y BaseClassB ambos heredan BaseClass, y luego DerivedClass hereda tanto BaseClassA como BaseClassB.

Si está realizando una conversión o sobrecarga del operador para convertir DerivedClass a BaseClassA o BaseClassB, necesitará identificar qué constructor (creo que algo así como un constructor de copia, IIRC) usar en la conversión.

2

En general, no llama al constructor directamente. El nuevo operador lo llama por usted o una subclase llama a los constructores de la clase padre. En C++, se garantiza que la clase base estará completamente construida antes de que comience el constructor de la clase derivada.

La única vez que llamaría directamente a un constructor es en el caso extremadamente raro en el que está administrando memoria sin usar nueva. E incluso entonces, no deberías hacerlo. En su lugar, debe usar la forma de ubicación del operador nuevo.

0

Existen casos de uso válidos en los que desea exponer un constructor de clases. Si desea hacer su propia gestión de memoria con un asignador de arena, por ejemplo, necesitará una construcción de dos fases que consista en la asignación y la inicialización de objetos.

El enfoque que tomo es similar al de muchos otros idiomas. Simplemente puse mi código de construcción en métodos públicos bien conocidos (Construct(), init(), algo así) y los llamo directamente cuando sea necesario.

Puede crear sobrecargas de estos métodos que coinciden con sus constructores; tus constructores regulares solo llaman a ellos. Ponga grandes comentarios en el código para advertir a los demás que está haciendo esto para que no agreguen código de construcción importante en el lugar equivocado.

Recuerde que solo hay un método destructor sin importar qué sobrecarga de construcción se utilizó, por lo que convierta sus destructores en robustos para los miembros no inicializados.

No recomiendo intentar escribir inicializadores que puedan volver a inicializarse. Es difícil decir en el caso en el que está mirando un objeto que solo tiene basura debido a la memoria no inicializada que a la retención de datos reales.

El problema más difícil se presenta con las clases que tienen métodos virtuales. En este caso, el compilador normalmente conecta el puntero de la tabla de funciones vtable como un campo oculto al inicio de la clase. Puede inicializar manualmente este puntero, pero básicamente depende del comportamiento específico del compilador y es probable que sus colegas lo vean divertido.

La ubicación nueva se ha roto en muchos aspectos; en la construcción/destrucción de matrices es un caso, así que tiendo a no usarlo.

0

Considere el siguiente programa.

template<class T> 
double GetAverage(T tArray[], int nElements) 
{ 
T tSum = T(); // tSum = 0 

for (int nIndex = 0; nIndex < nElements; ++nIndex) 
{ 
    tSum += tArray[nIndex]; 
} 

// Whatever type of T is, convert to double 
return double(tSum)/nElements; 
} 

Esto llamará explícitamente a un constructor predeterminado para inicializar la variable.

Cuestiones relacionadas