How to avoid std :: vector to copy (re) selection?

I just stumbled upon a problem 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 this by copying last element all the elements that he currently holds. This seems to suggest that any element in the vector is fully valid, and as such a copy will always be successful.

In our case, this is not necessarily the case. Currently, we may have some elements left over the vector, because we decided not to delete them, which are real objects, but their data does not guarantee correct behavior. Objects have guardians, but I never considered adding defenders to the copy constructor, since I assumed that we would never copy an invalid object (what vector forces):

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

It so happened that the "member" is reset to zero when we are done with the original object and leave it in the vector, so when std :: vector tries to copy it, it will work.

Can std::vector be prevented from copying this element? Or do we need to protect against copying invalid objects? Ideally, we would like to assume that only real objects are created, but this means that we immediately clean them of the vector, rather than expect and do it at a later stage.

+5
source share
3 answers

This is actually a design flaw in your CopiedClass that needs to be fixed.

std::vector behaves as expected and is documented.

From the little code you show

 member->DoSomething(); 

this means that you are going to take a shallow copy of the pointer to what ever.

in our case, this is not necessarily the case. Currently, we may have some elements left in the vector, because we decided not to delete them, which are real objects, but their data does not guarantee correct behavior.


It so happened that the "member" is reset to zero when we are done with the original object and leave it in the vector, so when std :: vector tries to copy it, it will work.

Since your copy constructor does not correctly handle this situation regarding rule 3 rule, your CopiedClass design CopiedClass incorrect.

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

Smart pointers should take good care of the management for these members.

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

Can std::vector be prevented from copying this element?

As you ask for this is possible, but requires that you never change the size of the vector and provide a move constructor and assignment operator for CopiedClass .

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

T must satisfy CopyAssignable and CopyConstructible .

Follow these requirements correctly.


... he does this by copying the last element that he currently holds.

Also note: in a situation, you need to resize the vector, all existing elements will be copied, and not just the last one, as you assume.

+7
source

If you know the maximum size of the vector in advance, then a workaround for this problem will call reserve with the maximum size. When the vector capacity is large enough, redistribution does not occur, and you do not need to copy existing elements.

But this is really just a workaround. You must redesign your class so that copying cannot suddenly become an invalid operation, either making the copy safe or disabling it, and possibly replacing it with a move, such as std::unique_ptr . If you make the copy reality dependent on some runtime state, you don’t even need std::vector to get into the problem. Simple CopiedClass get(); would be a potential mistake.

+2
source

It seems that when you try to add more elements to it, and it is necessary to allocate more space, it will do this by copying the last element at the moment. This, apparently, suggests that any element in the vector is fully valid, and therefore such a copy will always be successful.

Both sentences are not entirely correct. When a vector ends out of space, he, yes, performs a redistribution, but from all the elements, and they are copied to a new location or possibly moved. When you push_back or emplace_back , and redistribution is required, the element will usually be copied: if an exception is thrown at this time, it will be processed as if you had never added the element. If vector detects a user-defined move constructor noexcept ( MoveInsertable ), the elements are moved; if this constructor, although not noexcept , throws an exception, the result is not specified.

Objects have guards, but I never thought about adding guards to as I assumed, we will never copy an invalid object (what vector forces):

vector copies its value_type : it doesn’t care if it really contains or is invalid. You should take care of this in your copy management methods, whose scope precisely determines how the object is transferred.


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

The obvious solution would be to check if member valid and act in this way.

 if (member.internalData.isValid()) member->DoSomething() // acknowledge this.member data is invalid 

We don't know how member introduces, but Boost.Optional is what you can look for.

Can std :: vector be prevented from copying this element?

Redistribution is what the vector expects, so no, you cannot. reserv space could have avoided this, but maintained code for processing that would not actually be painless. Rather, prefer a container like std::forward_list / std::list .

Other solutions:

  • Hold unique_ptr<decltype(member)> , but pointers are not often a real solution.
  • Define the semantics of movement for your object, as described in other answers.
+2
source

Source: https://habr.com/ru/post/1234720/


All Articles