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

Shared pointers

Sometimes one object has several owners, and none of them knows when the others have finished with it. SharedPtr( of T ), from the std/memory module, keeps a count of owners: each copy adds one, each copy that goes away subtracts one, and the object is destroyed when the count reaches zero.

#import "std/memory"

'' One object, several owners; the last owner to go destroys it.
type Session
    as ustring user
    declare destructor( )
end type

destructor Session( )
    print "  session for "; user; " closed"
end destructor

'' byref: the procedure uses the object and does not become an owner.
sub Show( byref s as memory.SharedPtr( of Session ) )
    print "  "; s->user; ", owners:"; s.UseCount( )
end sub

var s = memory.MakeShared( of Session )( )     '' a new Session
s->user = "ada"                                '' -> reaches its fields
Show( s )

scope
    var t = s                                  '' a second owner
    Show( t )
end scope                                      '' t lets go; s remains
Show( s )

'' A SharedPtr can also adopt an object made with new.
var other = memory.SharedPtr( of Session )( new Session )
other->user = "grace"
Show( other )

print "reset both:"
s.Reset( )                                     '' the last owner of ada
other.Reset( )
print "s is null: "; s.IsNull( )

Output

  ada, owners: 1
  ada, owners: 2
  ada, owners: 1
  grace, owners: 1
reset both:
  session for ada closed
  session for grace closed
s is null: true
Procedure or memberDoes
memory.MakeShared( of T )( )a new T on the heap, owned by the returned pointer
memory.SharedPtr( of T )( p )adopt an object made with new; never delete it yourself
p->field, *pthe object, as through a raw pointer, with no check for null
p.Ref( )the object, checked: runtime error 1 on an empty pointer
p.UseCount( )how many SharedPtrs own the object
p.Reset( )stop owning; the object goes if this was the last owner
p.IsNull( )whether this pointer owns nothing

var t = s makes a second owner, and the count is 2 inside the scope. When t goes, the count drops back to 1 and the session stays. s.Reset( ) lets go of the last owner, and the destructor runs at that moment.

Show takes its pointer byref, so calling it makes no new owner. Pass a SharedPtr byval only when the procedure keeps a copy that outlives the call.

Cycles, and WeakPtr

Counting owners has one failure it cannot detect: two objects that own each other. A WeakPtr( of T ) observes an object without owning it, and is the tool for every link that points back.

#import "std/memory"

'' A parent owns its child; the child's link back must not own.
type Node
    as ustring tag
    as memory.SharedPtr( of Node ) child        '' owns, downward
    as memory.WeakPtr( of Node ) parent         '' observes, upward
    declare destructor( )
end type

destructor Node( )
    print "  freed "; tag
end destructor

'' The same shape with an owning link back: a cycle.
type LeakyNode
    as ustring tag
    as memory.SharedPtr( of LeakyNode ) child
    as memory.SharedPtr( of LeakyNode ) parent
    declare destructor( )
end type

destructor LeakyNode( )
    print "  freed "; tag
end destructor

print "owning both ways:"
scope
    var root = memory.MakeShared( of LeakyNode )( )
    root->tag = "leaky root"
    root->child = memory.MakeShared( of LeakyNode )( )
    root->child->tag = "leaky child"
    root->child->parent = root
end scope
print "  (nothing was freed)"

print "weak link back:"
scope
    var root = memory.MakeShared( of Node )( )
    root->tag = "root"
    root->child = memory.MakeShared( of Node )( )
    root->child->tag = "child"
    root->child->parent = root

    '' Lock( ) gives a real owner for as long as you hold it, or null.
    var up = root->child->parent.Lock( )
    if up.IsNull( ) = false then print "  the child's parent is "; up->tag
    print "  root owners:"; root.UseCount( )
end scope

Output

owning both ways:
  (nothing was freed)
weak link back:
  the child's parent is root
  root owners: 2
  freed root
  freed child

In the first tree the root owns the child and the child owns the root. When the scope ends, the two local pointers let go, but each node still has one owner, the other node, so neither count reaches zero and neither destructor runs. The memory is lost for the rest of the program, and nothing reports it.

In the second tree the link back is a WeakPtr. It does not count, so when root goes the root's count reaches zero, the root is freed, its child pointer goes with it, and the child follows.

A WeakPtr is read through Lock( ), which returns a real SharedPtr, an owner for as long as you hold it, or an empty one if the object has gone. That is why root owners is 2 while up exists. Ownership flows one way: make every link from owner to owned a SharedPtr, and every link back a WeakPtr.

Note

std/memory has no single-owner smart pointer. For an object used inside one procedure, a destructor or defer does that job.

Activities

  1. Print root.UseCount( ) after up.Reset( ).
  2. In weak.tiko, make the root's child the weak link and parent the owning one. What is freed, and when?