Copying
dim as Contact b = a makes b a copy of a. For most types that needs no
thought: the compiler copies every field, and each field knows how to copy
itself. A type that owns something through a pointer is different, and must
say what a copy means.
'' Fields that copy themselves need nothing more: the default copy
'' copies every field, and a ustring copies its text.
type Contact
fullName as ustring
phone as ustring
end type
dim as Contact a = ( "Ada", "555-0100" )
dim as Contact b = a '' copy
b.phone = "555-0199"
print a.fullName; " "; a.phone; " / "; b.fullName; " "; b.phone
'' A type that owns memory through a pointer must say what a copy
'' means, or two objects would share, and both free, one block.
type Samples
declare constructor( byval n as int32 )
declare constructor( byref other as Samples ) '' copy
declare operator let( byref other as Samples ) '' assign
declare destructor( )
items as int32 ptr
size as int32
end type
constructor Samples( byval n as int32 )
size = n
items = callocate( n, sizeof( int32 ) )
end constructor
'' A new object made from an existing one: nothing to free first.
constructor Samples( byref other as Samples )
size = other.size
items = allocate( size * sizeof( int32 ) )
for i as int32 = 0 to size - 1 : items[ i ] = other.items[ i ] : next
print " copy constructor"
end constructor
'' Assigning into an object that already owns a block: free it first,
'' unless the object is being assigned to itself.
operator Samples.let( byref other as Samples )
if @this = @other then exit operator
deallocate( items )
size = other.size
items = allocate( size * sizeof( int32 ) )
for i as int32 = 0 to size - 1 : items[ i ] = other.items[ i ] : next
print " operator let"
end operator
destructor Samples( )
deallocate( items )
end destructor
sub Inspect( byval s as Samples ) '' byval makes a copy, too
print " Inspect sees"; s.items[ 0 ]
end sub
dim as Samples x = Samples( 3 )
x.items[ 0 ] = 7
print "dim as Samples y = x"
dim as Samples y = x
y.items[ 0 ] = 8
print "z = x"
dim as Samples z = Samples( 1 )
z = x
z = z '' to itself: nothing happens
print "Inspect( x )"
Inspect( x )
print "x, y, z hold"; x.items[ 0 ]; y.items[ 0 ]; z.items[ 0 ]
Output
Ada 555-0100 / Ada 555-0199
dim as Samples y = x
copy constructor
z = x
operator let
Inspect( x )
copy constructor
Inspect sees 7
x, y, z hold 7 8 7
The default copy copies each field. Contact is two ustrings, and a
ustring copies its text, so changing b.phone leaves a alone. A type
built only from fields like these, numbers, strings and other well-behaved
objects, needs no copying code at all.
A pointer field is copied as a pointer. Without its own copy code,
Samples would give y the same items block as x: a write through one
would show in the other, and both destructors would free the same memory.
Two procedures prevent that:
| Procedure | Runs for | Must |
|---|---|---|
copy constructor, constructor( byref other as Samples ) | dim as Samples y = x, and every byval argument | make a new block and copy the values |
operator let( byref other as Samples ) | z = x, on an object that already exists | free the old block first, and do nothing when assigned to itself |
The self-assignment check in operator let matters: without it, z = z
would free z's block and then copy from the memory it had freed.
The output shows the costs as well. Inspect( x ) takes its parameter
byval, so a whole copy was made to call it. Pass an object byref, or
byref as const when the procedure only reads it, and no copy is made.
A type that needs a destructor almost always needs a copy constructor and
an operator let too: the destructor is there because the type owns
something, and the other two decide what happens to that something when the
object is duplicated. Write all three or none.
Activities
- Remove the
if @this = @otherline, runz = z, and printz.items[ 0 ]. What did you get, and why is it not reliable? - Change
Inspectto takebyref as const Samplesand check that the copy constructor no longer runs for it.