Falacia lógica
Tengo una clase que tiene un par de miembros de datos de tipo de clase. No quiero que se llame a los constructores de estos miembros cuando se declaren, por lo que estoy tratando de aferrarme a un puntero al objeto explícitamente.
Pensé que tal vez podría hacer lo siguiente, donde el constructor se llama inmediatamente al inicializar los miembros de datos:
class MyClass {
public:
MyClass(int n);
private:
AnotherClass another(100); // Construct AnotherClass right away!
};
pero quiero el MyClass
constructor para llamar al AnotherClass
constructor. Así es como se ve mi código:
BigMommaClass.h
#include "ThingOne.h"
#include "ThingTwo.h"
class BigMommaClass {
public:
BigMommaClass(int numba1, int numba2);
private:
ThingOne* ThingOne;
ThingTwo* ThingTwo;
};
BigMommaClass.cpp
#include "BigMommaClass.h"
BigMommaClass::BigMommaClass(int numba1, int numba2) {
this->ThingOne = ThingOne(100);
this->ThingTwo = ThingTwo(numba1, numba2);
}
Aquí está el error que recibo cuando intento compilar:
g++ -Wall -c -Iclasses -o objects/BigMommaClass.o classes/BigMommaClass.cpp
In file included from classes/BigMommaClass.cpp:1:0:
classes/BigMommaClass.h:12:8: error: declaration of âThingTwo* BigMommaClass::ThingTwoâ
classes/ThingTwo.h:1:11: error: changes meaning of âThingTwoâ from âclass ThingTwoâ
classes/BigMommaClass.cpp: In constructor âBigMommaClass::BigMommaClass(int, int)â:
classes/BigMommaClass.cpp:4:30: error: cannot convert âThingOneâ to âThingOne*â in assignment
classes/BigMommaClass.cpp:5:37: error: â((BigMommaClass*)this)->BigMommaClass::ThingTwoâ cannot be used as a function
make: *** [BigMommaClass.o] Error 1
¿Estoy usando el enfoque correcto, pero la sintaxis incorrecta? ¿O debería abordar esto desde una dirección diferente?
Puede especificar cómo inicializar miembros en la lista de inicializadores de miembros:
BigMommaClass {
BigMommaClass(int, int);
private:
ThingOne thingOne;
ThingTwo thingTwo;
};
BigMommaClass::BigMommaClass(int numba1, int numba2)
: thingOne(numba1 + numba2), thingTwo(numba1, numba2) {}
-
Todavía me molesta que con esta sintaxis, no puedo hacer ningún trabajo real en el constructor para pasar a los otros constructores. Los constructores probablemente deberían ser livianos, por lo que tengo problemas para encontrar un ejemplo del mundo real, pero me encuentro deseando que la sintaxis se parezca más a
this.thingOne = new ThingOne(100);
porque eso solo ofrece más flexibilidad. Pero yo divago.– Falacia lógica
17 de octubre de 2012 a las 5:11
-
Hace cosa uno y cosa dos tener para ser inicializado en el constructor de BigMommaClass? ¿O se puede hacer en alguna otra función que se llame después?
– jigglypuff
9 de febrero de 2018 a las 4:39
-
@nobismo, Para inicialización en lugar de asignación, tiene que hacerse en un constructor. Se le permite hacer algo como
: thingOne(calculateThing1(numba1, numba2))
. En casos raros, es posible que necesidad inicialización, pero no tengo forma de hacerlo en la lista de inicializadores. En ese caso, es posible usar algo comostd::optional
ots::deferred_construction
. Sin embargo, habría que pensar mucho en esa decisión.– chris
9 de febrero de 2018 a las 13:59
Estás tratando de crear un ThingOne
mediante el uso operator=
que no va a funcionar (sintaxis incorrecta). Además, está utilizando un nombre de clase como nombre de variable, es decir, ThingOne* ThingOne
. En primer lugar, arreglemos los nombres de las variables:
private:
ThingOne* t1;
ThingTwo* t2;
Dado que estos son punteros, deben apuntar a algo. Si el objeto aún no se ha construido, deberá hacerlo explícitamente con nuevo en su BigMommaClass
constructor:
BigMommaClass::BigMommaClass(int n1, int n2)
{
t1 = new ThingOne(100);
t2 = new ThingTwo(n1, n2);
}
Sin embargo, generalmente se prefieren las listas de inicializadores para la construcción, por lo que se verá así:
BigMommaClass::BigMommaClass(int n1, int n2)
: t1(new ThingOne(100)), t2(new ThingTwo(n1, n2))
{ }
-
gracias. Entonces debería tener
ThingOne* t1
o debería usarThingOne t1
?– Falacia lógica
17 de octubre de 2012 a las 4:42
-
@DavidEnglund, usa este último. La asignación dinámica innecesaria es mala. Si realmente lo necesitaba, un puntero inteligente habría sido el camino a seguir.
– chris
17 de octubre de 2012 a las 4:42
-
@cris, gracias. Estaba usando los punteros en un esfuerzo por evitar que los constructores fueran llamados de inmediato. Aunque creo que eso ya está aclarado.
– Falacia lógica
17 de octubre de 2012 a las 4:43
-
@DavidEnglund, solo quiero señalar que especificar argumentos en la declaración en la clase es solo C ++ 11 de todos modos, pero incluso no lo llama de inmediato. Cuando se inicializa ese objeto, ese será el valor predeterminado si no lo anula. Busque la inicialización de miembros en clase para obtener más información al respecto. Creo que también debe usar la inicialización uniforme para hacer eso si no es solo un constructor implícito con un parámetro con el que puede obtener
ThingOne thingOne = 100;
.– chris
17 de octubre de 2012 a las 4:45
-
@DavidEnglund Como han dicho otros, la asignación dinámica no es necesaria en este caso. Sin embargo, hay casos de uso para él (idioma pimpl, clases polimórficas, que solo requieren declaraciones hacia adelante para ayudar a reducir los tiempos de compilación, etc.).
– Yuushi
17 de octubre de 2012 a las 5:11
patmanpato
Esta pregunta es un poco antigua, pero aquí hay otra forma en C++ 11 de “hacer más trabajo” en el constructor antes de inicializar sus variables miembro:
BigMommaClass::BigMommaClass(int numba1, int numba2)
: thingOne([](int n1, int n2){return n1+n2;}(numba1,numba2)),
thingTwo(numba1, numba2) {}
Se invocará la función lambda anterior y el resultado se pasará al constructor de thingOnes. Por supuesto, puede hacer que la lambda sea tan compleja como desee.
-
los paréntesis no cuadran.
–Ted
26 de noviembre de 2019 a las 11:18
Iván
Sé que esto es 5 años después, pero las respuestas anteriores no abordan el problema con su software. (Bueno, el de Yuushi sí, pero no me di cuenta hasta que escribí esto – ¡doh!). Responden a la pregunta del título. ¿Cómo puedo inicializar las variables miembro del objeto C++ en el constructor? Esto es sobre las otras preguntas: ¿Estoy usando el enfoque correcto pero la sintaxis incorrecta? ¿O debería abordar esto desde una dirección diferente?
El estilo de programación es en gran medida una cuestión de opinión, pero una vista alternativa para hacer todo lo posible en un constructor es mantener los constructores al mínimo, a menudo con una función de inicialización separada. No hay necesidad de tratar de incluir todas las inicializaciones en un constructor, sin importar el intento de forzar las cosas a veces en la lista de inicialización de constructores.
Entonces, al punto, ¿qué estaba mal con su software?
private:
ThingOne* ThingOne;
ThingTwo* ThingTwo;
Tenga en cuenta que después de estas líneas, ThingOne
(y ThingTwo
) ahora tienen dos significados, dependiendo del contexto.
Fuera de BigMommaClass, ThingOne
es la clase con la que creaste #include "ThingOne.h"
Dentro de BigMommaClass, ThingOne
es un puntero.
Eso suponiendo que el compilador pueda incluso dar sentido a las líneas y no se quede atascado en un bucle pensando que ThingOne
es un puntero a algo que en sí mismo es un puntero a algo que es un puntero a…
Más tarde, cuando escribes
this->ThingOne = ThingOne(100);
this->ThingTwo = ThingTwo(numba1, numba2);
ten en cuenta que dentro de BigMommaClass
su ThingOne
es un puntero.
Si cambia las declaraciones de los punteros para incluir un prefijo (p)
private:
ThingOne* pThingOne;
ThingTwo* pThingTwo;
Entonces ThingOne
siempre se referirá a la clase y pThingOne
al puntero.
Entonces es posible reescribir
this->ThingOne = ThingOne(100);
this->ThingTwo = ThingTwo(numba1, numba2);
como
pThingOne = new ThingOne(100);
pThingTwo = new ThingTwo(numba1, numba2);
que corrige dos problemas: el problema del doble sentido y la falta new
. (Puedes irte this->
¡Si te gusta!)
Con eso en su lugar, puedo agregar las siguientes líneas a un programa mío en C++ y se compila muy bien.
class ThingOne{public:ThingOne(int n){};};
class ThingTwo{public:ThingTwo(int x, int y){};};
class BigMommaClass {
public:
BigMommaClass(int numba1, int numba2);
private:
ThingOne* pThingOne;
ThingTwo* pThingTwo;
};
BigMommaClass::BigMommaClass(int numba1, int numba2)
{
pThingOne = new ThingOne(numba1 + numba2);
pThingTwo = new ThingTwo(numba1, numba2);
};
cuando escribiste
this->ThingOne = ThingOne(100);
this->ThingTwo = ThingTwo(numba1, numba2);
el uso de this->
le dice al compilador que el lado izquierdo ThingOne
pretende significar el puntero. Sin embargo, estamos dentro BigMommaClass
en el momento y no es necesario.
El problema es con el lado derecho de los iguales donde ThingOne
pretende significar la clase. Entonces, otra forma de rectificar sus problemas habría sido escribir
this->ThingOne = new ::ThingOne(100);
this->ThingTwo = new ::ThingTwo(numba1, numba2);
o simplemente
ThingOne = new ::ThingOne(100);
ThingTwo = new ::ThingTwo(numba1, numba2);
usando ::
para cambiar la interpretación del compilador del identificador.
chico avraham
En cuanto a la primera (Y genial) respuesta de cris quien propuso una solución a la situación en la que los miembros de la clase son retenidos como “verdadero compuesto“miembros (es decir- no como punteros ni referencias):
La nota es un poco grande, así que la demostraré aquí con un código de muestra.
Cuando elige mantener a los miembros como mencioné, debe tener en cuenta también estas dos cosas:
-
Por cada “objeto compuesto” que no es tener un constructor por defecto – usted debe inicializarlo en la lista de inicialización de todo el constructor de la clase “padre” (es decir,
BigMommaClass
oMyClass
en los ejemplos originales yMyClass
en el código de abajo), en caso de que haya varios (verInnerClass1
en el ejemplo siguiente). Es decir, puede “comentar” elm_innerClass1(a)
ym_innerClass1(15)
solo si habilitas elInnerClass1
Constructor predeterminado. -
Por cada “objeto compuesto” que si tiene un constructor predeterminado – usted puede inicialícelo dentro de la lista de inicialización, pero también funcionará si elige no hacerlo (ver
InnerClass2
en el ejemplo siguiente).
Ver código de muestra (compilado bajo Ubuntu 18.04 (Castor biónico) con g++
versión 7.3.0):
#include <iostream>
class InnerClass1
{
public:
InnerClass1(int a) : m_a(a)
{
std::cout << "InnerClass1::InnerClass1 - set m_a:" << m_a << '\n';
}
/* No default constructor
InnerClass1() : m_a(15)
{
std::cout << "InnerClass1::InnerClass1() - set m_a:" << m_a << '\n';
}
*/
~InnerClass1()
{
std::cout << "InnerClass1::~InnerClass1" << '\n';
}
private:
int m_a;
};
class InnerClass2
{
public:
InnerClass2(int a) : m_a(a)
{
std::cout << "InnerClass2::InnerClass2 - set m_a:" << m_a << '\n';
}
InnerClass2() : m_a(15)
{
std::cout << "InnerClass2::InnerClass2() - set m_a:" << m_a << '\n';
}
~InnerClass2()
{
std::cout << "InnerClass2::~InnerClass2" << '\n';
}
private:
int m_a;
};
class MyClass
{
public:
MyClass(int a, int b) : m_innerClass1(a), /* m_innerClass2(a),*/ m_b(b)
{
std::cout << "MyClass::MyClass(int b) - set m_b to:" << m_b << '\n';
}
MyClass() : m_innerClass1(15), /*m_innerClass2(15),*/ m_b(17)
{
std::cout << "MyClass::MyClass() - m_b:" << m_b << '\n';
}
~MyClass()
{
std::cout << "MyClass::~MyClass" << '\n';
}
private:
InnerClass1 m_innerClass1;
InnerClass2 m_innerClass2;
int m_b;
};
int main(int argc, char** argv)
{
std::cout << "main - start" << '\n';
MyClass obj;
std::cout << "main - end" << '\n';
return 0;
}
¿Quiere llamarlo desde allí para poder usar los argumentos?
– chris
17 de octubre de 2012 a las 4:34
@chris, en este momento, creo que eso es todo lo que está pasando. Pero también me gustaría saber cómo hacer esto si, por ejemplo, necesito hacer algo antes de pasar los argumentos: como agregar “numba1” y “numba2” y pasar la suma a un constructor de variables miembro.
– Falacia lógica
17 de octubre de 2012 a las 4:37
Bueno, su error inmediato es que está asignando un objeto a un puntero (necesitaría un
new
, pero hay mejores alternativas de todos modos). Sin embargo, el problema en cuestión se puede resolver con los inicializadores de miembros.– chris
17 de octubre de 2012 a las 4:40
privado: otra clase otra (100); // ¡esto construye AnotherClass de inmediato! ¿Lo hace? Mi compilador realmente no acepta esto, a menos que la declaración esté dentro de una función. Esta línea se interpreta como una declaración de función y el compilador espera una lista de argumentos, no una constante o variable.
– Bart
2 de mayo de 2016 a las 10:49