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

Non-copyable types

A type that owns a resource has to decide what a copy means. If a Buffer that owns a block of memory were copied field by field, two objects would hold the same address, and two destructors would free it twice. For a resource with one owner, the right answer is usually to refuse copies.

'' A Buffer owns a block of memory for exactly as long as it lives.
'' A copy would share the block, and two destructors would free it
'' twice, so copying is declared private and never defined.
type Buffer
    declare constructor( byval length as int32 )
    declare destructor( )
    declare function Size( ) as int32
    declare function At( byval i as int32 ) byref as uint8
private:
    declare constructor( byref other as Buffer )    '' no copies
    declare operator let( byref other as Buffer )   '' no assignment
    as uint8 ptr bytes
    as int32 count
end type

constructor Buffer( byval length as int32 )
    bytes = callocate( length, 1 )
    count = length
    print "  allocated"; length; " bytes"
end constructor

destructor Buffer( )
    deallocate( bytes )
    print "  freed"; count; " bytes"
end destructor

function Buffer.Size( ) as int32
    return count
end function

function Buffer.At( byval i as int32 ) byref as uint8
    return bytes[i]
end function

'' Borrowing by reference is fine: no second owner is made.
function Total( byref b as Buffer ) as int32
    dim as int32 sum = 0
    for i as int32 = 0 to b.Size( ) - 1
        sum += b.At( i )
    next
    return sum
end function

scope
    dim block as Buffer = Buffer( 4 )
    for i as int32 = 0 to 3
        ( block.At( i ) ) = i * 10
    next
    print "  total"; Total( block )
end scope
print "after the scope"

Output

  allocated 4 bytes
  total 60
  freed 4 bytes
after the scope

The constructor allocates and the destructor frees, so the code that uses a Buffer contains no cleanup at all: block is freed at end scope.

The two private declarations are what make it safe. The copy constructor and operator let are declared and never defined, and because they are private, no code outside the type can reach them. Passing the buffer byref, as Total does, makes no copy, so it is allowed. Anything that would make a second owner is refused when the program is compiled:

type Buffer
    declare constructor( byval size as int32 )
private:
    declare constructor( byref other as Buffer )    '' no copies
    as int32 count
end type

constructor Buffer( byval size as int32 )
    count = size
end constructor

dim a as Buffer = Buffer( 4 )
dim b as Buffer = a                     '' a second owner? refused
nocopy.tiko(13,5): error TKO0633: 'b' needs a constructor or destructor that is not accessible here
     |
  13 | dim b as Buffer = a                     '' a second owner? refused
     |     ^

Assigning one buffer to another, b = a, reaches the private operator let instead and is refused with TKO0746: the operator let of 'buffer' is private or protected and cannot be reached from here. Passing a Buffer byval would copy it and is refused with TKO0633 too.

A type that owns something can copy it properly, be built from parts that copy themselves, or refuse to be copied. For a resource the system gives you once, such as a block of memory, a lock or a handle, refusing is usually right.

Activities

  1. Add a Fill( byval value as uint8 ) method and use it before Total.
  2. Write a Total that takes the buffer byval, and read the error.