I have a class where I need to use one of the members as a constructor parameter to initialize another const member of the same class.
class A
{
private:
M1Type m1;
const M2Type m2;
public:
A(x) : m1(x), m2(m1){}
};
is this a correct way to initialize m2? m1's construction is complete in the list initialization?
Update: Sorry I missed the type for x (was more of a pseudocode). Assume any primitive type.
CodePudding user response:
Yes, "m1's construction is complete" when it's used to initialize m2
and if an M2Type
can be constructed from an M1Type
, this is fine - but x
needs a type in A(x)
.
The order of initialization is the order in which you've defined the member variables in the class, not the order in which you use them in the member initializer list:
M1Type m1;
const M2Type m2;
A(M1Type x) : m2(m1), m1(x) {} // wrong order, but still ok
If the type of x
can be used to construct both an M1Type
and an M2Type
and get the same result as if you construct an M2Type
with an M1Type
constructed from x
, you might as well use x
to construct both to not risk problems if you reorganize the member variables later on. That's not always possible though.
Example:
struct M1Type {
explicit M1Type(double) {};
};
struct M2Type {
explicit M2Type(M1Type) {}
};
class A {
public:
M1Type m1;
const M2Type m2;
public:
// error: no matching function for call to 'M2Type::M2Type(double&)':
// A(double x) : m1(x), m2(x) {}
A(double x) : m1(x), m2(m1) {} // OK
};
CodePudding user response:
Members are initialized in the order they appear in the class definition (irrespective of the order in the initializer list). Hence, first m1
is initialized and you can use its value to initialize m2
.
This code
class A
{
private:
M1Type m1;
const M2Type m2;
public:
A(int x) : m1(x), m2(m1){}
};
Is fine (apart from missing type of x
). Though, if possible I would rather write it like this:
class A
{
private:
M1Type m1;
const M2Type m2;
public:
A(int x) : m1(x), m2(x){}
};
Because now correctness does not depend on the order of the members. It might change when refactoring. Compilers typically warn when the order in the initializer list is different, but instead of relying on a warning you can rely on code that is correct no matter what is the order of the members.
Note that compilers do not necessarily warn about this code (order of members swapped):
class A
{
private:
const M2Type m2;
M1Type m1;
public:
A(int x) : m2(m1),m1(x) {} // !! most likely UB !!
};
but it has undefined behavior if M2Type
s constructor uses the value of m1
, because m1
is used uninitialized. Hence, one should be careful when initialization of members depend on each other.
CodePudding user response:
Assuming M2Type has constructor accepting M1Type argument that should work.
PS: A(x) - type of x should be specified.
executing code below you can make sure it works:
#include <iostream>
class M1Type
{
public:
int iField;
M1Type(int x): iField{x}
{
std::cout << "M1Type(int x)" << iField << std::endl;
}
};
class M2Type
{
public:
int iField;
M2Type(M1Type m1) : iField{ m1.iField 500 }
{
std::cout << "M2Type(M1Type m1)" << iField << std::endl;
}
};
class A
{
private:
M1Type m1;
const M2Type m2;
public:
A(int x) : m1(x), m2(m1)
{
std::cout << "A(int x): M1Type: " << m1.iField << "; M2Type: " << m2.iField << std::endl;
}
};
int main()
{
A objA(73);
}
Console output would be:
M1Type(int x)73
M2Type(M1Type m1)573
A(int x): M1Type: 73; M2Type: 573