Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

ProcedureRuns forMust
copy constructor, constructor( byref other as Samples )dim as Samples y = x, and every byval argumentmake a new block and copy the values
operator let( byref other as Samples )z = x, on an object that already existsfree 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.

Pitfall

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

  1. Remove the if @this = @other line, run z = z, and print z.items[ 0 ]. What did you get, and why is it not reliable?
  2. Change Inspect to take byref as const Samples and check that the copy constructor no longer runs for it.