• Create Account

# Manual construction and destruction

Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

36 replies to this topic

### #21tivolo  Members   -  Reputation: 647

Like
1Likes
Like

Posted 02 November 2012 - 05:48 AM

How's that work? ::operator new() is saying "use the 'operator new' in global scope". Do classes have implicit operator new()s defined, and the implicit MyClass::operator new() then calls the class constructor? And the implicitly defined MyClass::operator delete() calls the class destructor?

I get that new() constructs the class, but what scope is 'new' in, if ::operator new() does not construct the class?

Short answer: There's a difference between the keyword new (called new operator) and operator new. Yeah, it's misleading .

If you're interested in details like these, I invite you to read more about that on my blog:
http://molecularmusings.wordpress.com/2011/07/05/memory-system-part-1/

There's five parts in the series, and they detail how to write your own memory manager and allocators.

HTH,
Stefan

### #22iMalc  Crossbones+   -  Reputation: 2098

Like
0Likes
Like

Posted 02 November 2012 - 12:43 PM

Okay, well that sounds fine.
When someone once wanted something like that, I wrote the below
template <typename T>
class growableMatrix
{
std::vector<T> contents;
size_t num_rows, num_cols;
public:
growableMatrix(size_t rows=0, size_t cols=0) :
contents(rows * cols), num_rows(rows), num_cols(cols) {}
T &operator()(size_t row, size_t col) {
if (row >= num_rows && col < num_cols) {
size_t new_num_rows = row+1;
assert(new_num_rows <= (std::numeric_limits<size_t>::max)() / num_cols); // For catching negative indexing
contents.resize(new_num_rows * num_cols);
num_rows = new_num_rows;
} else if (row >= num_rows || col >= num_cols) {
size_t new_num_rows = std::max(row+1, num_rows);
size_t new_num_cols = std::max(col+1, num_cols);
assert(new_num_rows <= (std::numeric_limits<size_t>::max)() / new_num_cols); // For catching negative indexing
std::vector<T> new_contents(new_num_rows * new_num_cols);
for (size_t i = 0; i<num_rows; ++i)
for (size_t j = 0; j<num_cols; ++j)
new_contents[i * new_num_cols + j] = contents[i * num_cols + j];
new_contents.swap(contents);
num_rows = new_num_rows;
num_cols = new_num_cols;
}
return contents[row * num_cols + col];
}
T operator()(size_t row, size_t col) const {
assert(row <= (std::numeric_limits<size_t>::max)() / col); // For catching negative indexing
if (row >= num_rows || col >= num_cols)
return T();
return contents[row * num_cols + col];
}
void swap(growableMatrix<T> &other) {
using std::swap;
contents.swap(other.contents);
std::swap(num_rows, other.num_rows);
std::swap(num_cols, other.num_cols);
}
size_t height() const { return num_rows; }
size_t width() const { return num_cols; }
};
Rather than being a fixed-size, this just resizes itself as you access elements that were outside of its bounds.
Also, as you can see, it can very much be useful to base your container on using a vector internally.
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms

### #23Matt-D  Crossbones+   -  Reputation: 1291

Like
0Likes
Like

Posted 03 November 2012 - 04:32 PM

There's two kinds of right shift -- arithmetic and logical. The former fills in the gap created at the top by "sign extending" (basically copying the top bit), and the later fills the gap with 0's.
IIRC, in C++ it's implementation-defined as to which kind of shift will be performed -- someone correct me if I'm wrong, or if C++11 has defined this behaviour!

C++ defines shift on a unsigned integer type to be logical shift, however, the standard leaves room for shifting a signed integer to be different from either a logical or arithmetic shift. It would be standard compliant under C++11 for a shift, either left or right, on a signed integer to be a rotation/circular shift rather than either a logical or arithmetic shift. Actually, under C++11 a left shift on a negative signed integer is undefined behavior rather than implementation defined behavior. (Don't ask me to explain this one. To be pedantic a left shift on a signed integer with some non-negative values is also undefined behavior as well, though there's a proposed change to the standard sitting in the committee to fix that oddity.)

Exactly, and this UB can result in some "interesting" effects at run-time:
http://blog.regehr.org/archives/738
http://blog.regehr.org/archives/759
http://blog.regehr.org/archives/767

### #24Servant of the Lord  Crossbones+   -  Reputation: 12521

Like
0Likes
Like

Posted 04 November 2012 - 11:11 PM

Here's the finished class I was creating - I think everything is working fine, after two days of trying to figure out how the heap was getting corrupted and looking in all the wrong locations.

#ifndef COMMON_CONTAINERS_GRID_H
#define COMMON_CONTAINERS_GRID_H
#include "Common/Basics.h"
class cPoint;
class cRect;
namespace Common
{
typedef int Type; //TEMP for development
/*
A resizable 2D array container, that resizes without harming the relative location of the elements held
by the container, and supports negative indices, like a 2D Cartesian grid.
*/
class Grid
{
public:
Grid();
Grid(const cRect &newBounds, const Type &value = Type()); //Calls Resize().
~Grid();

//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();
//Resets the grid to (0,0,0,0) and releases the reserved memory as well.
//This is the same as calling Clear() followed by Conserve().
void Reset();

//Returns true if the width *or* the height (or both) of the grid is 0, *even* if its position is *not* at (0,0).
bool IsEmpty() const;
//True if the grid is 0 by 0 *and* its position is at (0,0).
bool IsNull() const;

//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 throws any exceptions thrown by element constructors.
void Resize(const cRect &newBounds, const Type &value = Type());
const cRect &GetBounds() const;

//Reserves 'newCapacity' amount of memory, without actually resizing the grid. This will reallocate the memory and move the elements to new addresses.
//Reserve() will not allow the grid to shrink smaller than the capacity currently is reserved to.
//Throws std::bad_alloc if the allocation failed, and throws any exceptions thrown by element constructors.
void Reserve(const cRect &newCapacity);
//Releases all capacity that is no longer needed. This will reallocate the memory and move the elements to new addresses.
void Conserve();

//Returns the amount of memory held in reserve for future resizing.
const cRect &GetCapacity() const;

//Resizes the grid. Negative values shrink the grid.
void Expand(int amount);
void Expand(int horizontal, int vertical);
void Expand(int left, int right, int top, int bottom);

//Resizes the grid. Negative values enlarge the grid.
void Shrink(int amount);
void Shrink(int horizontal, int vertical);
void Shrink(int left, int right, int top, int bottom);

Type &At(const cPoint &pos); //Throws std::out_of_range if out of range.
Type &operator[](const cPoint &pos); //Doesn't check for range.

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());

//Construct a single element.
void construct(Type &element, const Type &value = Type());
//Constructs an element with move semantics.
void moveConstruct(Type &element, Type &&value);
//Destruct a single element.
void destruct(Type &element);

//Ensures *at least* enough room for 'bounds'.
//If 'addExtra' is true, includes even more capacity for future growth.
void ensureCapacity(const cRect &bounds, bool addExtra);

//This allocates enough memory for 'capacity', without constructing any elements.
Type *allocate(const cRect &capacity);
//This deallocates 'memory', without calling any destructors.
void deallocate(Type *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);
//Called by 'reallocateMemory' only.
void moveElements(Type *oldMemory, const cRect &oldCapacity, Type *newMemory,
const cRect &newCapacity, const cRect &bounds);
private:
Type *memory;

cRect bounds; //Current grid boundries.
cRect capacity; //Currently allocated memory capacity, garunteed to be at least 'bounds' and maybe larger.
};
} //End of namespace.
#endif // COMMON_CONTAINERS_GRID_H


Source:
#include "Grid.h"
#include <utility> //For std::move()
#include <stdexcept> //For std::out_of_range exception.
namespace Common
{
Grid::Grid() : memory(nullptr)
{   }
Grid::Grid(const cRect &newBounds, const Type &value) : memory(nullptr)
{
this->Resize(newBounds, value);
}
Grid::~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 Grid::Clear()
{
this->Resize(cRect(0,0,0,0));
}
//Resets the grid to (0,0,0,0) and releases the reserved memory as well.
//This is the same as calling Clear() followed by Conserve().
void Grid::Reset()
{
this->Clear();
this->Conserve();
}
//True if the grid is 0 by 0 in size, *even* if its position is *not* at (0,0).
bool Grid::IsEmpty() const
{
return this->bounds.IsEmpty();
}
//True if the grid is 0 by 0 *and* its position is at (0,0).
bool Grid::IsNull() const
{
return this->bounds.IsNull();
}

//Resizes the grid to 'newBounds'. If Grid doesn't have enough capacity, it'll reallocate storage and move the elements to new memory addresses.
void Grid::Resize(const cRect &newBounds, const Type &value)
{
this->ensureCapacity(newBounds, true);

this->bounds = newBounds;
}
const cRect &Grid::GetBounds() const
{
return this->bounds;
}

//Reserves 'rect' amount of memory, without actually resizing the grid. This will reallocate storage and move the elements to new memory addresses.
//Reserve() will not allow the grid to shrink smaller than the capacity currently is reserved to.
void Grid::Reserve(const cRect &newCapacity)
{
this->ensureCapacity(newCapacity, false);
}
//Releases all capacity that is no longer needed. This will reallocate the memory and move the elements to new addresses.
void Grid::Conserve()
{
this->reallocateMemory(this->bounds);
}
//Returns the amount of memory held in reserve for future resizing.
const cRect &Grid::GetCapacity() const
{
return this->capacity;
}

//Resizes the grid. Negative values shrink the grid.
void Grid::Expand(int amount)
{
this->Expand(amount, amount, amount, amount);
}
void Grid::Expand(int horizontal, int vertical)
{
this->Expand(horizontal, horizontal, vertical, vertical);
}
void Grid::Expand(int left, int right, int top, int bottom)
{
cRect newBounds = this->bounds;

this->Resize(newBounds);
}

//Resizes the grid. Negative values enlarge the grid.
void Grid::Shrink(int amount)
{
this->Shrink(amount, amount, amount, amount);
}
void Grid::Shrink(int horizontal, int vertical)
{
this->Shrink(horizontal, horizontal, vertical, vertical);
}
void Grid::Shrink(int left, int right, int top, int bottom)
{
cRect newBounds = this->bounds;

this->Resize(newBounds);
}
//Throws std::out_of_range if out of range.
Type &Grid::At(const cPoint &pos)
{
if(!this->bounds.Contains(pos))
{
std::string message = std::string("Grid::At() - 'pos' is not within range.\n")
+ "	'pos' = " + pos.ToString()
+ "\n	'bounds' = " + this->bounds.ToString();

throw std::out_of_range(message);
}

return (*this)[pos];
}
//Doesn't check for range.
Type &Grid::operator[](const cPoint &pos)
{
//Convert the point to a index into the memory.
size_t index = this->capacity.Index(pos);

return this->memory[index];
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX//
/////////////////////////////////////////////////////////////////////////////////////////////////////////
//Constructs all the new elements between oldBounds and newBounds.
//If 'newBounds' is smaller than 'oldBounds', deconstructs the old elements.
void Grid::constructAdditions(const cRect &oldBounds, const cRect &newBounds, const Type &value)
{
//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);

size_t constructed = 0, destructed = 0, preserved = 0, ignored = 0;

for(cPoint point : totalArea)
{
if(reducedArea.Contains(point))
{
//Do nothing - this area is already constructed.
preserved++;
}
else if(newBounds.Contains(point))
{
//Needs to be constructed.
this->construct((*this)[point], value);

constructed++;
}
else if(oldBounds.Contains(point))
{
//Needs to be destructed.
this->destruct((*this)[point]);

destructed++;
}
else
{
//Do nothing - this area is already destructed.

ignored++;
}
}
}
//Construct a single element.
void Grid::construct(Type &element, const Type &value)
{
new (&element) Type(value); //Call constructor.
}
//Constructs an element with move semantics.
void Grid::moveConstruct(Type &element, Type &&value)
{
new (&element) Type(value); //Call move constructor.
}
//Destruct a single element.
void Grid::destruct(Type &element)
{
element.~Type(); //Call destructor.
}
//Ensures *at least* enough room for 'bounds'.
void Grid::ensureCapacity(const cRect &bounds, bool addExtra)
{
//Check whether we have enough capacity to resize.
if(!this->capacity.Contains(bounds))
{
cRect desiredCapacity = bounds;

{
//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;

}

//Allocate and move the elements.
this->reallocateMemory(desiredCapacity);
}
}
//This allocates enough memory for 'capacity', without constructing any elements.
Type *Grid::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<Type*>(data);
}
//This deallocates 'memory', without calling any destructors.
void Grid::deallocate(Type *data)
{
::operator delete(data);
}
//Reallocates the memory and migrates the elements over using moveElements().
//Throws std::bad_alloc if the allocation failed.
void Grid::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.
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.
Type *newMemory = this->allocate(newCapacity);

//A few extra variables for readability.
Type *oldMemory = this->memory;
const cRect &oldCapacity = this->capacity;

//Move the elements.
this->moveElements(oldMemory, oldCapacity, newMemory, newCapacity, this->bounds);

//Delete the old memory.
this->deallocate(oldMemory);
oldMemory = nullptr;

//And store the new pointer.
this->memory = newMemory;
}

//Record the new capacity.
this->capacity = newCapacity;
}
//Called by 'reallocateMemory' only.
void Grid::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;

for(size_t row = 0; row < rows; row++)
{
for(size_t column = 0; column < columns; 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]));

//Destruct old location.
this->destruct(oldMemory[oldIndex]);
}
}
}

} //End of namespace.


(Note: This is dependant upon two other classes - cRect and cPoint. I can post them if anyone wants them, but I'm sure you can mostly guess how they work.
Note 2: The 'c' in cRect and cPoint does not stand for 'class' )

Now that it works, I'll convert it to a templated class and add iterators tomorrow.

Edited by SiCrane, 05 November 2012 - 08:06 AM.
tried to fix code tags

It's perfectly fine to abbreviate my username to 'Servant' rather than copy+pasting it all the time.

All glory be to the Man at the right hand... On David's throne the King will reign, and the Government will rest upon His shoulders. All the earth will see the salvation of God.                                                                                                                                                       [Need free cloud storage? I personally like DropBox]

### #25iMalc  Crossbones+   -  Reputation: 2098

Like
0Likes
Like

Posted 05 November 2012 - 02:04 AM

I'd love to see what you've done, but unfortunatly it all came out on one line. Any chance you can fix that?
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms

### #26Hodgman  Moderators   -  Reputation: 19247

Like
1Likes
Like

Posted 05 November 2012 - 07:12 AM

Something is seriously broken with the code tags at the moment... Here's SotL's post copied to pastebin so it's readable:
http://pastebin.com/thNj5nat

### #27Servant of the Lord  Crossbones+   -  Reputation: 12521

Like
0Likes
Like

Posted 05 November 2012 - 11:39 AM

Thanks Hodgeman, I was assuming the 'single line' code tags were a Chrome-specific bug - guess not.

It's perfectly fine to abbreviate my username to 'Servant' rather than copy+pasting it all the time.

All glory be to the Man at the right hand... On David's throne the King will reign, and the Government will rest upon His shoulders. All the earth will see the salvation of God.                                                                                                                                                       [Need free cloud storage? I personally like DropBox]

### #28Servant of the Lord  Crossbones+   -  Reputation: 12521

Like
0Likes
Like

Posted 05 November 2012 - 08:25 PM

Here's the final container, everything seems correct.
I converted it to a templated class, and added a basic iterator for range-for loops.

[pastebin]

It's perfectly fine to abbreviate my username to 'Servant' rather than copy+pasting it all the time.

All glory be to the Man at the right hand... On David's throne the King will reign, and the Government will rest upon His shoulders. All the earth will see the salvation of God.                                                                                                                                                       [Need free cloud storage? I personally like DropBox]

### #29SiCrane  Moderators   -  Reputation: 8488

Like
0Likes
Like

Posted 05 November 2012 - 08:59 PM

You have a pretty severe exception handling problem if any of the constructors throws inside constructAdditions(): you don't destroy any objects previously constructed in the function, which means that you'll be treating fully constructed objects as uninitialized memory in subsequent use of the class. Edit: and now that I think about it, you also need to reconstruct the objects you destroyed. Better do all the construction first and then destroy objects.

Edited by SiCrane, 05 November 2012 - 09:45 PM.

### #30Servant of the Lord  Crossbones+   -  Reputation: 12521

Like
0Likes
Like

Posted 05 November 2012 - 10:09 PM

Thanks for the heads up - moveElements() would have the same problem.

What's the best way to handle it, conceptually? I could catch the exception, <handle it>, then rethrow the exception.
The problem is, the entire class assumes everything within 'bounds' is constructed. I can't have one or two elements sporadically not constructed in the middle of constructed elements.
One option would be destructing anything constructed thus far, then freeing the memory, resetting bounds to zero, and basically resetting the Grid container to it's initial default-constructed state.
Another option would be just ensuring that that last failed call to Resize() is rolled back, but to preserve the state of everything up to that point.

It seems the potential problems are:
1) ensureCapacity() failing its memory allocation and throwing std::bad_alloc.
2) moveElements() (called by ensureCapacity()) failing to move-construct the new elements, or destruct the old elements.
3) constructAdditions() failing to construct or destruct elements being added or removed.

For problem '1', I could free all memory, reset Grid to it's initialization state, and throw my own std::bad_alloc with error message.
For problem '2' and 'problem '3', it seems like just rolling back the last Resize() (by destructing and freeing), and then rethrowing the exception I caught, would be a preferable solution.

Is there a better or more common method?

It's perfectly fine to abbreviate my username to 'Servant' rather than copy+pasting it all the time.

All glory be to the Man at the right hand... On David's throne the King will reign, and the Government will rest upon His shoulders. All the earth will see the salvation of God.                                                                                                                                                       [Need free cloud storage? I personally like DropBox]

### #31Servant of the Lord  Crossbones+   -  Reputation: 12521

Like
0Likes
Like

Posted 06 November 2012 - 12:39 AM

I think this covers it - any exception should roll back the Resize(), properly deconstructing any elements constructed.

[the entire code]
[just the relevant parts]

Did I make a mistake anywhere?

The code seems less straight-forward than before, but I guess that's what I get when I mess with manual allocation and deallocation.

It's perfectly fine to abbreviate my username to 'Servant' rather than copy+pasting it all the time.

All glory be to the Man at the right hand... On David's throne the King will reign, and the Government will rest upon His shoulders. All the earth will see the salvation of God.                                                                                                                                                       [Need free cloud storage? I personally like DropBox]

### #32SiCrane  Moderators   -  Reputation: 8488

Like
1Likes
Like

Posted 06 November 2012 - 07:57 AM

Your moveElements() function doesn't look right. When an exception occurs in the constructors and destroying elements, only the last row show go up to lastColumn, all the previous rows should go to columns. The way you've coded it looks like it misses destroying elements at the end of previous rows.

### #33Servant of the Lord  Crossbones+   -  Reputation: 12521

Like
0Likes
Like

Posted 06 November 2012 - 10:22 AM

Great catch - fixed.

It's perfectly fine to abbreviate my username to 'Servant' rather than copy+pasting it all the time.

All glory be to the Man at the right hand... On David's throne the King will reign, and the Government will rest upon His shoulders. All the earth will see the salvation of God.                                                                                                                                                       [Need free cloud storage? I personally like DropBox]

### #34SiCrane  Moderators   -  Reputation: 8488

Like
1Likes
Like

Posted 06 November 2012 - 10:36 AM

Also if reallocateMemory() is called with an empty newCapacity, it frees memory without destroying objects first.

### #35Servant of the Lord  Crossbones+   -  Reputation: 12521

Like
0Likes
Like

Posted 07 November 2012 - 11:17 AM

Excellent, fixed that, and simplified the for-loops.
[latest version] (If anyone wants to use the code, for whatever reason, it's public domain - slap your own name on it and use it in your code)

Thank you for all the help, everyone!
Any other mistakes in it I'll find through practical usage in my code.

Here's some example usage code.

It's perfectly fine to abbreviate my username to 'Servant' rather than copy+pasting it all the time.

All glory be to the Man at the right hand... On David's throne the King will reign, and the Government will rest upon His shoulders. All the earth will see the salvation of God.                                                                                                                                                       [Need free cloud storage? I personally like DropBox]

### #36SiCrane  Moderators   -  Reputation: 8488

Like
0Likes
Like

Posted 07 November 2012 - 12:43 PM

If that's your latest code then you also seem to have a rule of three violation: I don't see either a copy constructor or an assignment operator. And since you're using C++11's rvalue references you might also want to add a move constructor and move assignment operator. A nothrow swap function might also be a good idea.

### #37Servant of the Lord  Crossbones+   -  Reputation: 12521

Like
0Likes
Like

Posted 07 November 2012 - 11:15 PM

Thanks, I usually don't manually handle memory, so I often forget the rule of three.

Here's how I'm implementing it:
[relevant parts] [entire code]

Doing a few tests, I counted the number of destructions, constructions, moves, and copies, and the numbers balance out.

It's perfectly fine to abbreviate my username to 'Servant' rather than copy+pasting it all the time.

All glory be to the Man at the right hand... On David's throne the King will reign, and the Government will rest upon His shoulders. All the earth will see the salvation of God.                                                                                                                                                       [Need free cloud storage? I personally like DropBox]

Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

PARTNERS