template class Grid { public: Grid() : memory(nullptr) { } //Construct and call Resize(). Grid(const cRect &newBounds, const Type &value = Type()) : memory(nullptr) { this->Resize(newBounds, value); } ~Grid() { //Clear the grid, calling the destructors on any constructed elements. this->Clear(); //Deallocate any reserved memory. this->deallocate(this->memory); } //Erases the grid, resetting the bounds to (0,0,0,0). //Does not free the reserved capacity. Call Conserve() afterward to do that. void Clear() { this->Resize(cRect(0,0,0,0)); } //================================================================ //Resizes the grid to 'newBounds', constructing the new elements with 'value' (or the default constructor if 'value' is not set). //If Grid doesn't have enough capacity, it'll reallocate storage and move the elements to new memory addresses. //Throws std::bad_alloc if the allocation failed, and rethrows any exceptions thrown by element constructors or destructors. void Resize(const cRect &newBounds, const Type &value = Type()) { try { //[CAN THROW - Make sure no member variables are changed before these function calls] cRect newCapacity = this->ensureCapacity(newBounds, true); //[CAN THROW - Make sure no member variables are changed before these function calls] this->constructAdditions(this->bounds, newBounds, value); //Record the new capacity, after we are sure no exceptions were thrown and everything succeeded. this->capacity = newCapacity; } catch(std::exception &exception) { Log::Message("...blah..."); //exception.what() throw; } catch(...) { Log::Message("...blah..."); //Unknown exception throw; } this->bounds = newBounds; } //================================================================ private: //Constructs all the new elements between oldBounds and newBounds setting them to 'value'. //If 'newBounds' is smaller than 'oldBounds', deconstructs the old elements. void constructAdditions(const cRect &oldBounds, const cRect &newBounds, const Type &value = Type()) { //The largest extent of both rects. cRect totalArea = cRect::Encompassing(oldBounds, newBounds); //The overlapping portion of both rects (if any). cRect reducedArea = cRect::Intersection(oldBounds, newBounds); //------------------------------------------- //If a constructor throws an exception, we want to know which element it was, //so we keep track of what the last point was before the exception was thrown. cPoint lastPoint; try { for(cPoint point : newBounds) { if(reducedArea.Contains(point)) { //Do nothing - this area is already constructed. } else { lastPoint = point; //Needs to be constructed. this->construct((*this)[point], value); } } } catch(...) { //Since we caught an exception, destruct every element we had previously constructed. for(cPoint point : newBounds) { if(reducedArea.Contains(point)) { //Do nothing - this area is already constructed. } else if(point == lastPoint) { //Stop here - we didn't construct anything beyond this element. break; } else { //Destruct the element we previously constructed. this->destruct((*this)[point]); } } throw; } //------------------------------------------- //Destruct any elements that we no longer want (if we're resizing smaller than our previous size). for(cPoint point : oldBounds) { if(reducedArea.Contains(point)) { //Do nothing - we're preserving these elements. } else { //Needs to be destructed. this->destruct((*this)[point]); } } } //================================================================ //Construct a single element. void construct(Type &element, const Type &value = Type()) { new (&element) Type(value); //Call constructor. } //Constructs an element with move semantics. void moveConstruct(Type &element, Type &&value) { new (&element) Type(value); //Call move constructor. } //Destruct a single element. void destruct(Type &element) { element.~Type(); //Call destructor. } //================================================================ //Ensures *at least* enough room for 'bounds'. Returns the resulting capacity. //If 'addExtra' is true, includes even more capacity for future growth. cRect ensureCapacity(const cRect &bounds, bool addExtra) { //Check whether we have enough capacity to resize. if(!this->capacity.Contains(bounds)) { cRect desiredCapacity = bounds; if(addExtra) { //If we're bothering to grow in size, we might as well reserve a little extra for future growth. int quarterWidth = (bounds.size.width / 4) + 1; int quarterHeight = (bounds.size.height / 4) + 1; desiredCapacity.Pad(quarterWidth, quarterWidth, quarterHeight, quarterHeight); } //Allocate and move the elements. //[CAN THROW - Make sure no member variables are changed before this function call] this->reallocateMemory(desiredCapacity); //Return the new capacity. return desiredCapacity; } //Return the current capacity. return this->capacity; } //================================================================ //This allocates enough memory for 'capacity', without constructing any elements. Type *allocate(const cRect &capacity) { //Allocate the new memory. size_t numElements = capacity.size.Area(); size_t numBytes = (sizeof(Type) * numElements); void *data = ::operator new(numBytes); return static_cast(data); } //This deallocates 'memory', without calling any destructors. void deallocate(Type *data) { ::operator delete(data); } //================================================================ //Reallocates the memory and migrates the elements over using moveElements(). //Throws std::bad_alloc if the allocation failed, and throws any exceptions thrown by element constructors. void reallocateMemory(const cRect &newCapacity) { if(!this->memory) { //If we don't have any memory, just allocate some and don't worry about moving any elements. //[CAN THROW - Make sure no member variables are changed before this function call] this->memory = this->allocate(newCapacity); } else if(newCapacity.IsEmpty()) { //Free all the memory. this->deallocate(this->memory); this->memory = nullptr; } else { //Allocate the new memory. //Note: I put the allocated memory into a unique_ptr here, incase moveElements() throws. //This way, the new memory is properly released. //[CAN THROW - Make sure no member variables are changed before this function call] std::unique_ptr newMemory = this->allocate(newCapacity); //A few extra variables for readability. Type *oldMemory = this->memory; const cRect &oldCapacity = this->capacity; //Move the elements. //[CAN THROW - Make sure no member variables are changed before this function call] this->moveElements(oldMemory, oldCapacity, newMemory.get(), newCapacity, this->bounds); //Delete the old memory. this->deallocate(oldMemory); //And store the new pointer. Have the unique_ptr release ownership of the memory as well. this->memory = newMemory.release(); } //Record the new capacity. this->capacity = newCapacity; } //Called by 'reallocateMemory' only. void moveElements(Type *oldMemory, const cRect &oldCapacity, Type *newMemory, const cRect &newCapacity, const cRect &bounds) { //Insanity preservation. //Assert that our elements are actually within the capacity of both the new and old blocks of memory. BOOST_ASSERT(oldCapacity.Contains(bounds)); BOOST_ASSERT(newCapacity.Contains(bounds)); //Assert that neither block of memory's size is empty (they have to have a positive non-zero width and height). BOOST_ASSERT(!oldCapacity.IsEmpty()); BOOST_ASSERT(!newCapacity.IsEmpty()); //Assert that neither pointer to the allocated memory is empty. BOOST_ASSERT(oldMemory != nullptr); BOOST_ASSERT(newMemory != nullptr); //The length of each 'row' of the grid's capacity in memory. size_t oldStride = oldCapacity.size.width; size_t newStride = newCapacity.size.width; //The initial offset of the actual bounds from the memory capacity. size_t oldOffset = oldCapacity.Index(bounds.position); size_t newOffset = newCapacity.Index(bounds.position); //The number of rows and columns of actual elements we need to move. size_t rows = bounds.size.height; size_t columns = bounds.size.width; //================================================================= //Incase a constructor throws an exception, keep track of the last row and column. size_t lastRow = 0; size_t lastColumn = 0; try { //Move-construct all the new elements. for(size_t row = 0; row < rows; lastRow = row++) { for(size_t column = 0; column < columns; lastColumn = column++) { size_t oldIndex = (row * oldStride) + column; oldIndex += oldOffset; size_t newIndex = (row * newStride) + column; newIndex += newOffset; //Construct the new location, and move the element. this->moveConstruct(newMemory[newIndex], std::move(oldMemory[oldIndex])); } } } catch(...) { //Since we just caught an exception thrown from one of the element move-constructors, //destruct all the new elements we just constructed, up until the failed constructor. for(size_t row = 0; row < lastRow; row++) { //Go to the end of every row, but on the last row, just go to the last element we encountered. int &endOfColumn = (row == lastRow? lastColumn : columns); for(size_t column = 0; column < endOfColumn; column++) { size_t newIndex = (row * newStride) + column; newIndex += newOffset; //Destruct the element we constructed in the previous for() loops. this->destruct(newMemory[newIndex]); } } throw; } //================================================================= //Destruct all the old elements. for(size_t row = 0; row < rows; row++) { for(size_t column = 0; column < columns; column++) { size_t oldIndex = (row * oldStride) + column; oldIndex += oldOffset; //Destruct old location. this->destruct(oldMemory[oldIndex]); } } } //================================================================ private: Type *memory; cRect bounds; //Current grid boundaries. cRect capacity; //Currently allocated memory capacity, guaranteed to be at least 'bounds' and maybe larger. };