I came up with an unexpected behaviour (to my own limited knowledge) in the code below, where it does not respect the default member initialized values. I have a contractor for a single argument assignment that supposes to build the class from an assignment operator. I forgot to use the correct argument name and I ended up with this problem (see the line with single argument constructor with intentional error:
Why do I get garbage values and not the member initialized value?
My own assumption was because of templated class, 0 is not the same as 0.0...but tried it with and got the same problem.
#include <iostream>
#include <concepts>
template <class T>
requires std::is_arithmetic_v<T>
class Complex
{
private:
T re = 0;
T im = 0;
public:
Complex() {
std::cout << "Complex: Default constructor" << std::endl;
};
Complex(T real) : re{re} { // should be re{real}, but why re{re} is not 0?
std::cout << "Complex: Constructing from assignement!" << std::endl;
};
void setReal(T t) {
re = t;
}
void setImag(const T& t) {
im = t;
}
T real() const {
return re;
}
T imag() const {
return im;
}
Complex<T>& operator =(const Complex<T> other) {
re = other.re;
im = other.im;
return *this;
}
bool operator<(const Complex<T>& other) {
return (re < other.re && im < other.im);
}
};
int main() {
Complex<double> cA;
std::cout<< "cA=" << cA.real() << ", " << cA.imag() << "\n";
Complex<double> cB = 1.0; // Should print "1.0, 0" but prints garbage
std::cout<< "cB=" << cB.real() << ", " << cB.imag() << "\n";
Complex<int> cC = 1;
std::cout<< "cC=" << cC.real() << ", " << cC.imag() << "\n";
return 0;
}
Example output:
Complex: Default constructor cA=0, 0 Complex: Constructing from assignement! cB=6.91942e-310, 0 Complex: Constructing from assignement! cC=4199661, 0
The code on CompilerExplorer.
CodePudding user response:
Complex(T real) : re{re} { // should be re{real}, but why re{re} is not 0?
If you provide an initializer for a member explicitly in the constructor, then this initializer replaces the default member initializer. The default member initializer is in such a case not used at all.
To be clear: The default member initializers are not initializing the members prior to the constructor call. They just "fill up" the intializers for members not mentioned in the constructor's member initializer list.
In your case re{re}
accesses an object outside it's lifetime (re
), causing undefined behavior.
Also, as a side note: Complex<double> cB = 1.0;
and Complex<int> cC = 1;
are not assignments. Both are declarations with initialization. The =
is not part of an assignment expression and doesn't call operator=
as an assignment would. They are both copy-initialization, in contrast to Complex<double> cB(1.0);
which is direct-initialization.
The constructor Complex(T real)
used by either of these initializations is called a converting constructor.