sancho3 wrote:That decorator type would work with either of our stacks.
Indeed, but they should
both derive from another intermediate class (called an
interface) that declares the relevant methods (push() and pop() in this case) as
abstract. Like this:
Code: Select all
type IStack extends Object
declare abstract sub push( byval as any ptr )
declare abstract function pop() as any ptr
declare abstract property count() as uinteger
end type
Type SanchoStack extends IStack
As any Ptr item
As SanchoStack Ptr prev_item, last_item
Declare Sub push(ByVal pm As any Ptr) override
Declare Function pop() As any Ptr override
declare property count() as uinteger override
Declare Destructor
private:
m_count as uinteger
End Type
Destructor SanchoStack()
'
Dim As SanchoStack Ptr p = this.last_item
While p <> 0
Dim As SanchoStack Ptr temp = p
p = p->prev_item
Delete temp
temp = 0
Wend
End Destructor
'---------------------------------------------------------
property SanchoStack.count() as uinteger
return( m_count )
end property
Sub SanchoStack.push(ByVal mi As any ptr)
'
? "Now pushing to Sancho's Stack"
Dim As SanchoStack Ptr ptemp = this.last_item
this.last_item = New SanchoStack
this.last_item->item = mi ' store the item
this.last_item->prev_item = pTemp ' point the new last item->prev item to the old last item
m_count += 1
End Sub
Function SanchoStack.pop() As any Ptr
'
? "Now poping from Sancho's Stack"
Dim As SanchoStack Ptr p = this.last_item
If p = 0 Then Return 0
this.last_item = p->prev_item
Function = p->item
Delete p
p = 0
m_count -= 1
End Function
/'
Simple Stack
This will be the base object to decorate
'/
type Stack extends IStack
public:
declare constructor( byval as uinteger = 256 )
declare destructor()
declare sub push( byval as any ptr ) override
declare function pop() as any ptr override
declare property count() as uinteger override
declare property size() as uinteger
protected:
declare constructor()
declare constructor( byref as Stack )
declare operator let( byref as Stack )
private:
m_items( any ) as any ptr
m_size as uinteger
m_count as uinteger
end type
constructor Stack( byval pSize as uinteger = 256 )
? "Stack::Stack()"
m_size = iif( pSize < 1, 1, pSize )
m_count = 0
redim m_items( 0 to m_size - 1 )
end constructor
destructor Stack()
? "Stack::~Stack()"
end destructor
sub Stack.push( byval value as any ptr )
? "Now pushing to Paul's Stack"
if( m_count < m_size - 1 ) then
m_count += 1
m_items( m_count - 1 ) = value
else
'' Handle stack overflow
end if
end sub
function Stack.pop() as any ptr
? "Now poping from Paul's Stack"
if( m_count > 0 ) then
dim as any ptr ret = m_items( m_count - 1 )
m_count -= 1
return( ret )
else
'' Handle stack underflow
end if
end function
property Stack.count() as uinteger
return( m_count )
end property
property Stack.size() as uinteger
return( m_size )
end property
/'
This is a decorator class for the Stack
It accepts a single argument in its constructor, another
object to decorate (change or augment it's functionality).
In this case, since FB doesn'any allow to overload a function's
based only on it's return parameter, this can be a cool
alternative.
Decorator class modify the run-time behavior of an object, and
add/remove some functionality from the decorated class. In this
case, all it does is decorate a Stack instance to push() and
pop() only integers.
'/
type DecoratedStack extends Object
public:
declare constructor( byval as IStack ptr )
declare destructor()
declare sub push( byval as integer )
declare function pop() as integer
declare property count() as uinteger
declare property stack( byval as IStack ptr )
protected:
declare constructor( byref as DecoratedStack )
declare operator let( byref as DecoratedStack )
private:
m_instance as IStack ptr
end type
constructor DecoratedStack( byval instance as IStack ptr )
? "DecoratedStack::DecoratedStack()"
m_instance = instance
end constructor
destructor DecoratedStack()
? "DecoratedStack::~DecoratedStack()"
end destructor
property DecoratedStack.stack( byval instance as IStack ptr )
m_instance = instance
end property
sub DecoratedStack.push( byval value as integer )
dim as integer ptr i = new integer
*i = value
m_instance->push( i )
end sub
function DecoratedStack.pop() as integer
dim as integer ptr p = m_instance->pop()
dim as integer i = *p
'' Dispose of the pointer here
delete( p )
return( i )
end function
property DecoratedStack.count() as uinteger
return( m_instance->count )
end property
/'
Main program
Creates a Stack object and then decorates it with another
object called DecoratedStack. In this example, all it does
is storing and retrieving integers from the pointer stack,
but of course all kinds of complex functionality can be
implemented in the decorator.
'/
dim as Stack ptr myStack = new Stack( 10 )
dim as SanchoStack ptr otherStack = new SanchoStack()
dim as DecoratedStack ptr myDecoratedStack = _
new DecoratedStack( myStack )
?
myDecoratedStack->push( 3 )
myDecoratedStack->push( 5 )
myDecoratedStack->push( 8 )
?
myDecoratedStack->stack = otherStack
myDecoratedStack->push( 9 )
myDecoratedStack->push( 4 )
myDecoratedStack->push( 2 )
?
? myDecoratedStack->pop()
? myDecoratedStack->pop()
? myDecoratedStack->pop()
?
myDecoratedStack->stack = myStack
? myDecoratedStack->pop()
? myDecoratedStack->pop()
? myDecoratedStack->pop()
?
/'
Note that the unhandled dereference in DecoratedStack.pop() will
cause the app to crash on overflows and underflows.
This may or may not be the desired behavior, so if you want the
app to fail gracefully, handle them.
? myDecoratedStack->pop() <-- this will crash
'/
/'
Normally, decorating classes don'any delete the objects they
decorate, as they're only meant to provide ad-hoc functionality.
So, you need to manually delete the decorated object.
'/
delete( myDecoratedStack )
delete( otherStack )
delete( myStack )
sleep()
In this code, you can see that the decorator uses the IStack interface to use both implementations of the stack transparently.
sancho3 wrote:I stayed away from any ptrs. thinking that the user would have to cast them to their original type, but that does not appear to be the case in your code. I just test my stack using any ptrs and indeed it does need casting.
Yes, of course, you need to cast an any ptr to be able to dereference it. But, have a closer look inside the push() method of the decorator:
Code: Select all
sub DecoratedStack.push( byval value as integer )
dim as integer ptr i = new integer
*i = value
m_instance->push( i )
end sub
The IStack interface expects a pointer, so while the decorated stack accepts an integer, inside we create an integer ptr, assign it the value passed through push(), and push it to the underlying stack implementation (whichever it is).
sancho3 wrote:The one thing though is if the stack is not emptied, and the decorator goes out of scope, then your decorator leaks the memory of those items left.
No.
The decorator only maintains a pointer to an interface, so it doesn't need to deallocate anything. If you do, you'll destroy the decorated object, and that is not always the desired result. Look at the code example to see what I mean.
sancho3 wrote:Don't you think you should loop through the remaining items and pop them in the decorator.destructor?
Neither the stack nor the decorator should free memory that they don't create, but they should free that which they do.
Yes, that's why I provided a 'count' method in my implementation. However, the example code doesn't need to, because poping from the stack through the decorator deletes it (but return the value it contained to the caller). If we were to do this with the bare stack implementation, we should first cast it to a datatype to be able to delete it. Look:
Code: Select all
function DecoratedStack.pop() as integer
dim as integer ptr p = m_instance->pop()
dim as integer i = *p
'' Dispose of the pointer here
delete( p )
return( i )
end function
So you see, no leak,
as long as you pop through the decorator. Should you want to delete remaining items in the stack, you can do it like this:
Code: Select all
myDecoratedStack->stack = myStack '' Switch to my implementation of the stack
do while myDecoratedStack->count > 0
myDecoratedStack->pop()
loop
In the example is not necessary, since the stack remains empty. But it's a good point nonetheless.
You can of course raise a runtime error when popping, but that's also easy to avoid, and I intentionally left it untouched. Look at my implementation of the stack, it does indeed check for underflows and overflows. The decorator doesn't,
it provides different functionality. In this particular case, wrapping the IStack interface (that accepts any ptrs) into one that accepts integers. If you want to implement this in the decorator, simply check against a null pointer, and don't do anything if there's no pointer returned.
Bottom line: the responsibility for memory deallocation is a question of convention (and of course, usability issues). Since I
segregate data from the data structures that act on it, there's another object that takes care of the deallocation of data, just not the data structures. Think of an object that only contains data (or
collections of it) that other data structures manipulate in a way similar to what's shown here. That way,
data is immutable, whereas data structures are not. And also, you can use the same data with the data structure that you deem the most convenient, at any time, without the need to move data in any way,
you just work with pointers to that data.
This is a very flexible and powerful concept, that you'll find very useful, but one does need time to get accustomed to it.