Move semantics with pointer to internal buffer

Suppose I have a class that controls a pointer to an internal buffer:

class Foo { public: Foo(); ... private: std::vector<unsigned char> m_buffer; unsigned char* m_pointer; }; Foo::Foo() { m_buffer.resize(100); m_pointer = &m_buffer[0]; } 

Now suppose I also correctly applied material from the 3rd level, including the copy constructor, which copies the internal buffer and then reassigns the pointer to a new copy of the internal buffer:

 Foo::Foo(const Foo& f) { m_buffer = f.m_buffer; m_pointer = &m_buffer[0]; } 

If I also implement move semantics, can I just copy the pointer and move the buffer?

 Foo::Foo(Foo&& f) : m_buffer(std::move(f.m_buffer)), m_pointer(f.m_pointer) { } 

In practice, I know that this should work, because the std::vector move constructor simply moves the internal pointer - it does not actually redistribute anything, so m_pointer still points to a valid address. However, I am not sure that the standard guarantees this behavior. Does the semantics of std::vector move to ensure that redistribution does not happen, and therefore all pointers / iterators to the vector are valid?

+6
source share
4 answers

I would do &m_buffer[0] again, just so you don't have to ask these questions. This is clearly not obvious intuitive, so do not do this. And with that, you have nothing to lose. Win-win.

 Foo::Foo(Foo&& f) : m_buffer(std::move(f.m_buffer)) , m_pointer(&m_buffer[0]) {} 

I like it, mainly because m_pointer is a representation in the m_buffer member, not just the member of its choice.

That all this asks a question ... why is this so? Could you open a member function to give you &m_buffer[0] ?

+4
source

I will not comment on the OP code. All I do is ask this question:

Does the semantics of std :: vector convey that redistribution does not happen, and therefore all pointers / iterators to the vector are valid?

Yes for the move constructor. It has constant complexity (as indicated in 23.2.1 / 4, table 96 and note B), and for this reason the implementation has no choice but to steal the memory from the original vector (therefore, the memory will not be reallocated) and to empty the original vector .

No to the operator of assignment of movement. The standard requires only linear complexity (as indicated in the same paragraph and table mentioned above), because redistribution is sometimes required. However, in some systems this can be of constant complexity (and redistribution is not performed), but it depends on the distributor. (You can read the excellent exposure on the Howard Hinnant vector page here .)

+5
source

The best way to do this could be:

 class Foo { std::vector<unsigned char> m_buffer; size_t m_index; unsigned char* get_pointer() { return &m_buffer[m_index]; }; 

those. instead of saving a pointer to a vector element, save its index. Thus, it will be immune to copying / resizing the backup vector storage.

+2
source

It is guaranteed that the case of constructing the movement moves the buffer from one container to another, therefore, from the point of view of the newly created object, the operation is beautiful.

On the other hand, you should be careful with such code, since the donor object remains with an empty vector and a pointer that refers to a vector in another object. This means that after moving from your object, a fragile state occurs that can cause problems if someone accesses the interface and, more importantly, if the destructor tries to use the pointer.

While there will be no use at all of your object after moving it from (assuming that it is associated with an rvalue reference, it must be an rvalue), the fact is that you can go from an lvalue by casting or using std::move (which is mostly cast), in which case the code may actually try to use your object.

+1
source

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


All Articles