I'm trying to initialize the values
vector with 25 values of 3
(for example) using the following code:
#include <vector>
#include <iostream>
class VecClass {
public:
VecClass(int v)
: values(25, v),
var1(new int(v)),
var2(v){}
public:
std::vector<int> values; // not initialized
int* var1; // initialized
int var2; // initialized
};
class BaseClass {
public:
BaseClass(const VecClass& ref)
: spec(ref) {}
public:
const VecClass& spec;
};
int main() {
BaseClass ref = BaseClass(VecClass(3));
std::cout << "*var1 = " << *ref.spec.var1 << "\n"; // output : 3
std::cout << "var2 = " << ref.spec.var2 << "\n"; // output : 3
std::cout << "size of values = " << ref.spec.values.size() << "\n"; // output : 0 (expected : 25)
}
It's working fine for the members var1
and var2
that are initialized to 3. However, it doesn't work for vector member values
that should be initialized with 25 elements, all set to 3.
I'm using Visual Studio 2022.
I would like to understand why values
is not initialized in this context and how to initialize it using BaseClass
as showed in the example above.
I've tested with const VecClass& spec = VecClass(3);
and it's working fine, so I believe the issue might be related with the BaseClass
constructor.
The problem is with the spec
reference. It refers to an object. What object does it refer to, where does it "live"? How long does it live? If you answer this, you will find the answer to your question.
This program makes it more obvious:
#include <iostream>
#include <vector>
class VecClass {
public:
VecClass(int v) : values(25, v), var1(new int(v)), var2(v) {}
public:
std::vector<int> values; // not initialized
int* var1; // initialized
int var2; // initialized
};
class BaseClass {
public:
BaseClass(VecClass const& ref) : spec(ref) {}
public:
VecClass const& spec;
};
int main() {
auto* this_obj_needs_to_be_alive = new VecClass(3);
BaseClass ref = BaseClass(*this_obj_needs_to_be_alive);
std::cout << "*var1 = " << *ref.spec.var1 << "\n"; // output : 3
std::cout << "var2 = " << ref.spec.var2 << "\n"; // output : 3
std::cout << "size of values = " << ref.spec.values.size() << "\n"; // output : 25
delete this_obj_needs_to_be_alive;
// everything beyond this point is undefined behavior
std::cout << "*var1 = " << *ref.spec.var1 << "\n"; // output : who knows
std::cout << "var2 = " << ref.spec.var2 << "\n"; // output : who knows
std::cout << "size of values = " << ref.spec.values.size() << "\n"; // output : who knows
}
This prints something like
*var1 = 3
var2 = 3
size of values = 25
*var1 = 3
var2 = 3
size of values = 7973933
Of course, the last three lines could print anything, or even crash, or sing the national anthem because indirecting through the reference to the destructed VecClass
object is Undefined Behavior.
The easiest thing here is to avoid references and pointers if you can, and otherwise make sure that the referred-to-object outlives the reference/pointer.
Why did var1
/var2
appear to work?
It's because they're much simpler (primitive) types and they simply "appear" ok because they haven't yet been overwritten in memory. This is purely accidental though.
Note how the compiler is able to warn for the simple example I posted, and says:
warning: pointer used after 'void operator delete(void*, std::size_t)' [-Wuse-after-free]
main.cpp:31:12: note: call to 'void operator delete(void*, std::size_t)' here
31 | delete this_obj_needs_to_be_alive;
Not all problems will be diagnosed at compiletime, but you'll catch way more if you compile with warnings enabled
Catch some of the remaining problems with sanitizers: -fsanitize=undefined,address
on Clang and GCC