I have java/python experience and recently started working on a C project. I ran into this very generic problem and wasn't able to find any clear answer/explanation in StackOverflow. The concepts behind is probably very basic, but still most of the things I tried seems to fail this far, so I thought it is worth to post it here and learn the "proper C way".
I'm trying to create a vector of objects in a for loop. The code I have is something like below which works:
int NUMBER_OF_OBJECTS = 6;
std::vector<SomeObject> objects = std::vector<SomeObject>(NUMBER_OF_OBJECTS);
for (int i = 0; i < NUMBER_OF_OBJECTS; i ) {
std::string name = "Object" std::to_string(i);
test::SetObject(&objects[i], name.data());
}
however if I try something like:
std::vector<SomeObject> objects;
for (int i = 0; i < 6; i ) {
std::unique_ptr<SomeObject> object = std::make_unique<SomeObject>();
objects.push_back(*object.get());
std::string name = "Object" std::to_string(i);
test::SetObject(&objects[i]);
}
it doesn't work (ie below prints the same address):
for (SomeObject o : objects) {
std::cout << "object address: " << &o << "\n";
}
I thought that's because the docs of unique_ptr says "The object is destroyed and its memory deallocated when ... unique_ptr managing the object is destroyed". So I tried to preserve the unique_ptr by creating a vector of it, something like:
std::vector<std::unique_ptr<SomeObject>> objects;
for (int i = 0; i < 6; i ) {
std::unique_ptr<SomeObject> object = std::make_unique<SomeObject>();
objects.push_back(object);
std::string name = "Object" std::to_string(i);
test::SetObject(&(*objects[i].get()));
}
but instead of preserving the unique_ptr, this instead still destroys the object and warns me call to implicitly-deleted copy constructor of 'std::unique_ptr<SomeObject>'
I guess putting the unique_ptr to a vector is not enough to make C not destroy that unique_ptr, maybe I need to do something like std::move? I feel like something as basic as initialising a list of objects is probably done very frequently and should have a simpler way to it.
I would greatly appreciate if anyone could elaborate how memory management works in C for practical purposes (eg. I know I could use new, but read that in most cases I should use make_unique instead to avoid leaking memory)
CodePudding user response:
It's not your other code that is flawed, it's the loop where you print the addresses to the objects:
for (SomeObject o : objects) {
std::cout << "object address: " << &o << "\n";
}
Here a brand new o
object will be created each iteration of the loop. But nothing stops the compiler to reuse the same memory for the object o
.
If you want to get each "unique" object from the container, use references instead:
for (SomeObject const& o : objects) { ... }
CodePudding user response:
std::unique_ptr<SomeObject> object = std::make_unique<SomeObject>();
objects.push_back(*object.get());
can be simplified to
SomeObject object{};
objects.push_back(object);
or even
objects.emplace_back();
Your issue in 2nd/3rd snippet is that vector is resized, so inernal pointer and iterator are invalidated:
test::SetObject(&objects[i]); // dangling pointer after objects.push_back(*object.get());
std::vector::reserve
might solve your issue:
std::vector<SomeObject> objects;
object.reserve(6);
for (int i = 0; i < 6; i ) {
SomeObject object{};
objects.push_back(object);
std::string name = "Object" std::to_string(i);
test::SetObject(&objects[i]);
}
or use vector of (smart) pointer:
std::vector<std::unique_ptr<SomeObject>> objects;
// object.reserve(6); // No longer needed
for (int i = 0; i < 6; i ) {
std::unique_ptr<SomeObject> object = std::make_unique<SomeObject>();
objects.push_back(std::move(object));
// or simply
// objects.push_back(std::make_unique<SomeObject>());
std::string name = "Object" std::to_string(i);
test::SetObject(objects[i].get());
}