Skip to content

ftlSharedPtr

Robert Rüger edited this page Apr 28, 2017 · 4 revisions

ftlSharedPtr is a smart pointer that retains shared ownership of an object through a pointer. Several ftlSharedPtr objects may own the same object. The object is destroyed and its memory deallocated when either of the following happens:

  • the last remaining ftlSharedPtr owning the object goes out of scope
  • the last remaining ftlSharedPtr owning the object is assigned another pointer via assignment(=) or is nullified.

The object is destroyed through deallocation and optionally through calling custom destructor method on it.

The object whose lifetime is managed by the ftlSharedPtr is accessible through the value data member.

subroutine DoesNotLeak

   integer, pointer :: i
   type(ftlSmartPtrInt) :: p1, p2

   allocate(i)
   i = 42

   call p1%AssumeOwnershipOf(i)
   write (*,*) p1%UseCount(), p1%Unique() ! prints: 1 True
   p2 = p1 ! p2 and p1 now share ownership of i
   write (*,*) p1%UseCount(), p1%Unique() ! prints: 2 False
   call p1%Nullify() ! p2 is now the only owner
   write (*,*) p2%UseCount(), p2%Unique() ! prints: 1 True

   write (*,*) p2%value ! prints 42

   ! i will be deallocated by the finalizer of p2 at the end of the
   ! subroutine ...

end subroutine

ftlSharedPtr is basically a reimplementation of C++'s std::shared_ptr, though the interface is a bit more explicit.

Instantiation

Macros

ftlSharedPtr uses the following macros for instantiation:

FTL_TEMPLATE_TYPE
The type of the objects to be managed by the shared pointer. Do not add the enclosing type() for derived types.
FTL_TEMPLATE_TYPE_IS_DERIVED
This needs to be defined if FTL_TEMPLATE_TYPE is a derived type.
FTL_TEMPLATE_TYPE_MODULE
The name of the module in which FTL_TEMPLATE_TYPE is defined. Specifying this is probably only necessary if FTL_TEMPLATE_TYPE is a derived type.
FTL_TEMPLATE_TYPE_NAME
A convenient user-readable name for the managed type, e.g. Int. This will be used in the type-name of the ftlSharedPtr itself and the module in which it is available, e.g. setting this to Int will result in the type ftlSharedPtrInt available in module ftlSharedPtrIntModule.
FTL_INSTANTIATE_TEMPLATE
If this macro is defined the template will be instantiated. If not, the template will not be instantiated.

Methods and public data members

TODO: example usage for all methods

Object access and ownership management

ftlSharedPtr%value

Raw Fortran pointer associated with the managed object. Should be used to access the pointed to object, like one would use the dereference operator on C++'s std::shared_ptr.

While it is technically possible to manually reassociate this pointer with another object (aka ftlSharedPtr%value => someOtherObject), this should never be done. The originally pointed to object will not be cleaned up properly! This caveat could be avoided by making value private and providing a getter function, but this would prevent direct access to members of the managed object (aka ftlSharedPtr%value%someMember), which would be very inconvenient. So never reassociate ftlSharedPtr%value directly. Use it like you would dereference a pointer in C/C++ ...

ftlSharedPtr%Allocate()

Constructs a shared pointer to a newly allocated object.

subroutine Allocate(self)
   type(ftlSharedPtrT), intent(out) :: self

ftlSharedPtr%AssumeOwnershipOf()

Constructs a shared pointer that assumes the ownership of an object pointed to by a (owning!) raw Fortran pointer.

subroutine AssumeOwnershipOf(self, p)
   type(ftlSharedPtrT), intent(out) :: self
   T, pointer         , intent(in)  :: p

After construction the lifetime of the object pointed by p is managed by the shared pointer. The raw pointer should therefore not be deallocated manually. It will be deallocated when the last shared pointer to it goes out of scope, so deallocating it manually would result in a double deallocation.

ftlSharedPtr assignment(=)

Assignment from another ftlSharedPtr is used to share ownership of an object:

subroutine assignment(=)(lhs, rhs)
   type(ftlSharedPtrT), intent(out) :: self
   type(ftlSharedPtrT), intent(in)  :: other

If rhs is associated the ownership of the pointed object is shared by both ftlSharedPtrs. The last one to go out of scope takes care of deleting it. If rhs is not associated, lhs is also not associated after the assignment. (Note that the two shared pointers are independently unassociated. They will not automagically become associated to the same object if only one of them is associated with it.)

ftlSharedPtr%Nullify()

subroutine Delete(self)
   type(ftlSharedPtrT), intent(inout) :: self

Nullifies an instance of a shared pointer (causing it to become unassociated), but before has the following side effects:

  • If the UseCount of the shared pointer is greater than one (meaning there are other shared pointers pointing to the same object), it will be decreased by one.
  • If the UseCount of the shared pointer is one (meaning it is the only shared pointer currently pointing to the object), the object will be deallocated (and its finalizer will be called).
  • If the shared pointer is already unassociated, nothing happens.

Note that it is not necessary to call Nullify() on a ftlSharedPtr manually. The method is called automatically as the finalizer of ftlSharedPtr, though it might be useful to call it manually in order to release resources early.

Ownership diagnostics

ftlSharedPtr%Associated()

Checks the association status of a shared pointer, similar to how the associated() Fortran intrinsic works for raw pointers.

Just like for the Fortran intrinsic, there are three different versions.

  • Check if a shared pointer is associated to anything at all.

    pure logical function Associated(self)
       type(ftlSharedPtrT), intent(in) :: self

    Note that all ftlSharedPtrs start out being unassociated. This is different from normal Fortran pointers which (for some reason) start out being undefined unless they are initialized with => null() in the declaration. This is not necessary for ftlSharedPtr. If you don't associate your shared pointer with anything, it's unassociated. Period.

  • Check if two shared pointers are associated with the same target.

    pure logical function Associated(self, other)
       type(ftlSharedPtrT), intent(in) :: self
       type(ftlSharedPtrT), intent(in) :: other
  • Check if a shared pointer is associated with the same target as a raw pointer. (This is the case, when the shared pointer is managing the lifetime of the object pointed to by the raw pointer.)

    pure logical function Associated(self, p)
       type(ftlSharedPtrT), intent(in) :: self
       T, pointer         , intent(in) :: p

ftlSharedPtr%UseCount()

Returns the number of shared pointers currently sharing the ownership of the associated object.

pure integer function UseCount(self)
   type(ftlSharedPtrT), intent(in) :: self

ftlSharedPtr%Unique()

Return whether or not the shared pointer is the only shared pointer currently associated with the object. This is equivalent to checking whether or not UseCount() == 1.

pure logical function Unique(self)
   type(ftlSharedPtrT), intent(in) :: self

Note that unassociated shared pointers are not considered unique.

FTL utility methods

ftlSwap()

ftlSharedPtr%Swap()

Swaps two shared pointers, transferring ownership of any managed object between them without destroying or altering the UseCount of either.

subroutine Swap(self, other)
   type(ftlSharedPtrT), intent(inout) :: self
   type(ftlSharedPtrT), intent(inout) :: other

The same effect could also be achieved with assignment and a temporary shared pointer:

type(ftlSharedPtrInt) :: sp1, sp2, tmp
tmp = sp2
sp2 = sp1
sp1 = tmp
! ^-- same result as ftlSwap(sp1, sp2)

Using ftlSwap() is more convenient though, and (slightly) faster.

ftlMove()

Transfers the ownership of an object from one shared pointer to another. The useCount is unchanged and the source shared pointer becomes unassociated in the process.

subroutine ftlMove(src, dest)
   type(ftlSharedPtrT), intent(inout) :: src
   type(ftlSharedPtrT), intent(out)   :: dest