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 member | Does |
|---|---|
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, *p | the 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.
std/memory has no single-owner smart pointer. For an object used inside one
procedure, a destructor or defer does that job.
Activities
- Print
root.UseCount( )afterup.Reset( ). - In
weak.tiko, make the root'schildthe weak link andparentthe owning one. What is freed, and when?