Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid std::vector to copy on (re-)allocation?

Tags:

c++

c++11

vector

I just stumbled into an issue with std::vector when adding new elements to it.

It seems when you try to add more elements to it, and it needs to allocate more space, it does so by copying last element all elements it currently holds. This seems to assume that any element in the vector is fully valid, and as such a copy will always succeed.

In our case, this is not necessarily true. Currently we might have some left over elements in the vector, because we chose not to remove them yet, which are valid objects, but their data doesn't guarantee valid behavior. The objects have guards, but I never considered adding guards to a copy constructor as I assumed we would never be copying an invalid object (which vector forces) :

CopiedClass::CopiedClass(const CopiedClass& other)
    : member(other.member)
{
    member->DoSomething();
}

It just so happened that "member" is nulled when we are done with the original object and leave it lying about in the vector, so when std::vector tries to copy it, it crashes.

Is it possible to prevent std::vector from copying that element? Or do we have to guard against possible invalid objects being copied? Ideally we'd like to keep assuming that only valid objects are created, but that seems to imply we immediately clean them from the vector, rather than waiting and doing it at some later stage.

like image 816
Zepee Avatar asked Oct 28 '15 17:10

Zepee


2 Answers

It's actually a design flaw in your CopiedClass that needs to be fixed.

std::vector behaves as expected and documented.

From the little code you show

member->DoSomething();

this indicates you are going to take a shallow copy of a pointer to what ever.

in our case, this is not necessarily true. Currently we might have some left over elements in the vector, because we chose not to remove them yet, which are valid objects, but their data doesn't guarantee valid behavior.


It just so happened that "member" is nulled when we are done with the original object and leave it lying about in the vector, so when std::vector tries to copy it, it crashes.

As your copy constructor doesn't handle that situation correctly regarding following the Rule of Three your design of CopiedClass is wrong.

You should either care to create a deep copy of your member, or use a smart pointer (or a plain instance member), rather than a raw pointer.

The smart pointers should take care of the management for these members correctly.

Also for the code snippet from above, you should test against nullpointer before blindly dereferencing and calling DoSomething().

Is it possible to prevent std::vector from copying that element?

As you're asking for c++11 it's possible, but requires you never change the size of the vector, and provide a move constructor and assignment operator for CopiedClass.

Otherwise, std::vector explicitly requires copyable types (at least for certain operations):

T must meet the requirements of CopyAssignable and CopyConstructible.

It's your responsibility to meet these requirements correctly.


... it does so by copying the last element it currently holds.

Also note: In a situation the vector needs to be resized, all of the existing elements will be copied, not only the last one as you premise.

like image 177
πάντα ῥεῖ Avatar answered Sep 28 '22 02:09

πάντα ῥεῖ


If you know the vector's maximum size in advance, then a workaround for this issue would be calling reserve with that maximum size. When the vector's capacity is big enough, no reallocations occur and no copies of existing elements need to be made.

But that's really just a workaround. You should redesign your class such that copying cannot suddenly become an invalid operation, either by making copying safe or by disallowing it and perhaps replace it with moving, like std::unique_ptr. If you make the validity of copying dependent on some run-time status, then you don't even need a std::vector to get into trouble. A simple CopiedClass get(); will be a potential error.

like image 41
Christian Hackl Avatar answered Sep 28 '22 03:09

Christian Hackl