I have a vector of objects that represent GPU resources. I ended up with a pretty dangerous scenario: the objects can only be safely created or destroyed in a specific thread, but another thread still needs to be able to move them around between vectors.
As a safeguard, I deleted their copy constructor, copy assignment, and move assignment methods; the only method that isn't deleted is their move constructor.
Is there a way I can move an object like this from the middle of one vector, to the back of another vector?
class MappedBuffer {
public:
MappedBuffer(GLuint name, char * mapping) : m_name(name), m_mapping(mapping) {
assert(std::this_thread::get_id() == RENDER_THREAD_ID);
};
MappedBuffer(MappedBuffer && other) : m_name(other.m_name), m_mapping(other.m_mapping) {
other.m_name = 0;
other.m_mapping = 0;
}
MappedBuffer(const MappedBuffer & other) =delete;
MappedBuffer & operator=(MappedBuffer && other) =delete;
MappedBuffer & operator=(const MappedBuffer & other) =delete;
~MappedBuffer() {
assert(m_name == 0 || std::this_thread::get_id() == RENDER_THREAD_ID);
if (m_name) {
gl::buffer::unmap(m_name);
gl::buffer::dealloc(m_name);
}
}
private:
char * m_mapping {0}; // directly points to GPU memory
GLuint m_name {0};
};
// not the render thread
int f(size_t srcIndex, std::vector<MappedBuffer> & src, std::vector<MappedBuffer> & dest) {
// how could someone do something like the following, but without destroying any of the objects?
dest.push_back(src.begin() srcIndex);
dest.erase(src.begin() srcIndex);
}
CodePudding user response:
Two vectors do not share an allocation (except if one is move-constructed/-assigned from the other).
Therefore moving between vectors implies creating a new object in the new vector's allocation and destroying the one in the old allocation.
Only node-based containers offer an interface to reassign elements between containers without a move of the stored object, e.g. std::list::splice
.
The alternative is to add an indirection to the vector, by storing pointers (of whatever kind) to the objects instead of the objects themselves in it.
Without indirection per element this can't work.
You can't insert into the vector either under these constraints. Insertion may cause reallocation and consequently move and destruction of objects.
However, the type you are showing already contains an indirection via the char*
pointer. Your class sets m_name
to zero and therefore an "empty" state if it is moved-from and the destructor won't actually do anything. So destruction after a move is completely safe. The move assignment can simply behave in exactly the same way. And then there won't be any issue at all.
As long as the copy operations are deleted, the container can only destroy an object in a moved-from state or if deletion of elements is explicitly requested (e.g. .clear()
). Otherwise destroying would mean dropping an object with a value that could have never had a copy made, implying that the container would lose information that it may not lose. The container also can't use a move assignment to overwrite a non-empty object for the same reason, except when erasure of an element is requested.