tk3 (home, dev, source, bugs, help)

RPGCodeObject Oriented Coding — Copy Constructors

Constructors that take only one parameter are cast constructors. They are used to cast data to the type of your class. Cast constructors can be useful in making classes feel more "natural" without overloading operators that can sometimes produce ambiguous results. Consider the following example.

class CString
{
public:
	method CString(str$) { m_str$ = str$ }
	method operator$() { returnMethod(m_str$) }
private:
	m_str$
}

method test(CString str)
{
	show(str)
	wait()
	mwincls()
}

test("Hello, world!")

When test() is called, passing in a string literal, a temporary object-to be destroyed at the end of the line-of type CString is constructed from CString::CString() which saves the string passed in to its member. Later, it casts this object to type string in order to display it. Remember, this object will be destroyed when the line reaches its end, as it is only temporary. It is important to understand that all objects that are directly constructed are merely temporary. Consider the following:

test(CString("Hello, world!"))

That line is directly equivalent to the one seen earlier. Next, observe this line:

str = CString("Hello, world!")

Even the object created on the right-hand-side is temporary. The object on the left-hand-side is created by copying that on the right. First, it looks for a cast constructor that takes the type of the object itself-a copy constructor-and if one exists, it is called. However, if one doesn't exist, the left-hand-side's object is created by simply copying the values of the right-hand-side's members to a new object. In this case, that works. In many cases, it works. Do not trick yourself into coding redundant copy constructors.

That said, there are indeed some cases in which a copy constructor is required for execution of copying to succeed. The following is an example of code which will not work:

class CCanvas
{
public:
	method CCanvas(x!, y!)
	{
		m_cnv! = createCanvas(x!, y!)
		m_width! = x!
		m_height! = y!
	}
	method pixel(x!, y!) { setPixel(x!, y!, m_cnv!) }
	method draw(x!, y!) { drawCanvas(m_cnv!, x!, y!) }
	method ~CCanvas() { killCanvas(m_cnv!) }
private:
	m_cnv!
	m_width!
	m_height!
}

cnv = CCanvas(50, 50)
cnv->pixel(5, 5)
cnv->draw(0, 0)
wait()

Running this code provides you with no error messages, but a blank screen. Why? In the line that creates the canvas object, the left-hand-side is created via copying (as seen above). The problem lies in the fact that the canvas handle is copied over, but when the right-hand-side is destroyed at the end of the line, that very same canvas handle is killed. This can be remedied by providing a copy constructor:

method CCanvas(CCanvas rhs!)
{
	m_width! = rhs->m_width!
	m_height! = rhs->m_height!
	m_cnv! = createCanvas(m_width!, m_height!)
	drawCanvas(rhs->m_cnv!, 0, 0, m_width!, m_height!, m_cnv!)
}

This function performs a deep-copy-one that uses logic (direct copies being shallow)-by creating a new canvas the size of the right-hand-side and then drawing its canvas onto our new one. Thus, copying the canvas, but leaving us with a new handle. If you code a copy constructor, you also need to provide an assignment operator. There are two integral differences between the two, however. The assignment operator must check to make sure it's not being to itself, and clean up before copying. An assignment operator for the above class could be coded like this:

method operator=(CCanvas rhs!)
{
	// Prevent setting to self
	if (this! ~= rhs!)
	{
		// Clean up
		killCanvas(m_cnv!)
		// Now just copy over
		m_width! = rhs->m_width!
		m_height! = rhs->m_height!
		m_cnv! = createCanvas(m_width!, m_height!)
		drawCanvas(rhs->m_cnv!, 0, 0, m_width!, m_height!, m_cnv!)
	}
}

In conclusion, for many simple classes a shallow copy is more than sufficient. Whatever you do, don't add a copy constructor just because. Evaluate your logic to decide whether possible flaws exist. Look for code that allocates and frees memory (dynamic arrays have this behavior too, though it's not as apparent) for a sign that a copy constructor is needed.


previous, forward