5555
Проблема перекрёстных ссылок. Как её решить?

 

> Имею два объекта, каждый из которых ссылается на другого.
> При их уничтожении они остаются в памяти. Имеется ли способ корректного их удаления?
> Вот простой пример:

*//////////////////////////////////// save as any.prg
ACTIVATE SCREEN
CLEAR
PUBLIC goObj1, goObj2

goObj1 = NewObject("MyCls1")
goObj2 = NewObject("MyCls2")
goObj1.oProp2 = goObj2
goObj2.oProp1 = goObj1

DO cleanup

DEFINE CLASS MyCls1 As Session
	Name = "MyCls1"
	oProp2 = NULL
	PROCEDURE Destroy
		ACTIVATE SCREEN
		? "Destroy: " +  this.Name 
		this.oProp2 = NULL
	ENDPROC
ENDDEFINE

DEFINE CLASS MyCls2 As Session
	Name = "MyCls2"
	oProp1 = NULL
	PROCEDURE Destroy
		ACTIVATE SCREEN
		? "Destroy: " +  this.Name 
		this.oProp1 = NULL
	ENDPROC
ENDDEFINE

PROCEDURE cleanup
ACTIVATE SCREEN
? "Call Cleanup"
IF Type('goObj1') = 'O' AND !IsNull(goObj1)
	RELEASE goObj1
ENDIF	
IF Type('goObj2') = 'O' AND !IsNull(goObj2)
	RELEASE goObj2
ENDIF	
*/////////////////////////////// the end of any.prg

> Если проследить работу приведённого выше примера в отладчике,
> то можно легко увидеть, что события Destroy() у обоих классов не отрабатывают :-(


Да, это так. Проблема в том, что событие Destroy() не может быть выполнено потому, что есть свойство-ссылка на существующий объект... и чтобы избавиться, необходимо выполнить её уничтожение до события Destroy() объекта. Например так:

*//////////////////////////////////// save as any.prg
ACTIVATE SCREEN
CLEAR
PUBLIC goObj1, goObj2

goObj1 = NewObject("MyCls1")
goObj2 = NewObject("MyCls2")
goObj1.oProp2 = goObj2
goObj2.oProp1 = goObj1

DO cleanup

DEFINE CLASS MyCls1 As Session
	Name = "MyCls1"
	oProp2 = NULL
	
	PROCEDURE RemoveAllRefs
		this.oProp2 = NULL
	ENDPROC
	
	PROCEDURE Destroy
		ACTIVATE SCREEN
		? "Destroy: " +  this.Name 
		this.RemoveAllRefs() && Let's hope that in the future versions it will be was correctly :-)
	ENDPROC
ENDDEFINE

DEFINE CLASS MyCls2 As Session
	Name = "MyCls2"
	oProp1 = NULL

	PROCEDURE RemoveAllRefs
		this.oProp1 = NULL
	ENDPROC
	
	PROCEDURE Destroy
		ACTIVATE SCREEN
		? "Destroy: " +  this.Name 
		this.RemoveAllRefs() 
	ENDPROC
ENDDEFINE

PROCEDURE cleanup
ACTIVATE SCREEN
? "Call Cleanup"
IF Type('goObj1') = 'O' AND !IsNull(goObj1)
	goObj1.RemoveAllRefs()
	RELEASE goObj1
ENDIF	
IF Type('goObj2') = 'O' AND !IsNull(goObj2)
	goObj2.RemoveAllRefs()
	RELEASE goObj2
ENDIF	
*/////////////////////////////// the end of any.prg
ниже аналогичный пример для классов, производных от Form
*//////////////////////////////////// save as any.prg
#DEFINE USE_RELEASE	.F.
ACTIVATE SCREEN
CLEAR
PUBLIC goObj1, goObj2

goObj1 = NewObject("MyCls1")
goObj2 = NewObject("MyCls2")
goObj1.oProp2 = goObj2
goObj2.oProp1 = goObj1

DO cleanup

DEFINE CLASS MyCls1 As Form
	Name = "MyCls1"
	oProp2 = NULL
	PROCEDURE Destroy
		ACTIVATE SCREEN
		? "Destroy: " +  this.Name 
		this.oProp2 = NULL
	ENDPROC
ENDDEFINE

DEFINE CLASS MyCls2 As Form
	Name = "MyCls2"
	oProp1 = NULL
	PROCEDURE Destroy
		ACTIVATE SCREEN
		? "Destroy: " +  this.Name 
		this.oProp1 = NULL
	ENDPROC
ENDDEFINE

PROCEDURE cleanup
ACTIVATE SCREEN
? "Call Cleanup"
IF Type('goObj1') = 'O' AND !IsNull(goObj1)
	#IF USE_RELEASE
		goObj1.Release()
	#ENDIF	
	RELEASE goObj1
ENDIF	
IF Type('goObj2') = 'O' AND !IsNull(goObj2)
	#IF USE_RELEASE
		goObj2.Release()
	#ENDIF	
	RELEASE goObj2
ENDIF
*/////////////////////////////// the end of any.prg

Обратите внимание, что явный вызов метода Release() у форм (при USE_RELEASE .T.), снимает проблему. В том смысле, что вынуждает выполниться событию Destroy() экземпляра объекта.

Замечено также, что если вы храните ссылки в массиве, то для корректного удаления соответствующих объектов необходимо явное присвоение NULL соответствующему элементу массива, т.е.

gaMyObjects[i] = NULL

А в свете сказанного выше относительно метода Release() у классов производных от Form, луше так:

LOCAL luTemp
	...
	luTemp = gaMyObjects[i]
	IF TYPE('luTemp') = 'O' AND !ISNULL(luTemp)
	   IF INLIST(luTemp.BaseClass, "Form", "FormSet", "ToolBar") 
	      *-- Insert any special-destroy actions here... 
	      luTemp.Release()
	   ENDIF
	ENDIF
	STORE NULL TO ;
	   luTemp, gaMyObjects[i]
	...
Однако, явный вызов метода Release() формы может и не привести к её разрушению. Это может случиться например из-за того, что ссылка на один из объектов, принадлежащий разрушаемой форме, где-нибудь используется во вне её.
*//////////////////////////////////// save as any.prg
*// Demo example problems 
*// with cross-refs in forms
*// VFP ver: 8.0(SP1VFP8), 7.0(SP1VFP7), 6.0(SP5VS6)
*//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*// Author:  Michael Drozdov
*// My Page: http://vfpdev.narod.ru/
*// Date:    11/12/2003
*//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
*-- If USE_CLEARPROPS is true (& USE_REMOVECHILDOBJS .F.) 
*-- works correctly, only in case clear all external references
*-- by explicit use command: RELEASE goMyAnyFrm or method: goMyAnyFrm.Release()
#DEFINE USE_CLEARPROPS		.F. 
*
*-- If USE_REMOVECHILDOBJS is true 
*-- all works correctly, ...and there are problems otherwise
#DEFINE USE_REMOVECHILDOBJS	.F.
*
*-- For explicit use command: RELEASE goMyAnyFrm
#DEFINE USE_CLEANUP	.F.
*
*-- (if USE_CLEANUP	.T.) For explicit use method: goMyAnyFrm.Release()
#DEFINE USE_RELEASE	.F.

PUBLIC goFrm1
ACTIVATE SCREEN
CLEAR

*-- Create Form1 
goFrm1 = NEWOBJECT('Frm1')
IF VARTYPE(goFrm1) # 'O'
	DO cleanup
	RETURN .F.
ENDIF
goFrm1.Show()

*-- Create Form2 
PUBLIC goFrm2
goFrm2 = NEWOBJECT('Frm2')
IF VARTYPE(goFrm2) # 'O'
	DO cleanup
	RETURN .F.
ENDIF
goFrm2.Show()

*-- Init cross references 
*-- between child objects of the forms
goFrm1.oCmdExit2 = goFrm2.cmdExit
goFrm2.oCmdExit1 = goFrm1.cmdExit

#IF USE_CLEANUP
	DO cleanup
#ENDIF

DEFINE CLASS Frm1 As Form
	Caption = "Form1"
	oCmdExit2 = NULL
	Left = 120
	ADD OBJECT cmdExit As CommandButton WITH ;
		Caption = "Cancel",;
		Top = 5
	PROCEDURE cmdExit.Click()
		ThisForm.Release() 
	ENDPROC	 
	PROCEDURE Destroy()
		ACTIVATE SCREEN 
		?"Destroy: "+ThisForm.Caption  
		#IF USE_CLEARPROPS
			*-- Release property to external ref
			this.oCmdExit2 = NULL
		#ENDIF	 
		#IF USE_REMOVECHILDOBJS
			*-- Release object, the reference on which 
			*-- can be used by external objects
			this.RemoveObject('cmdExit')
		#ENDIF	 
	ENDPROC	 
ENDDEFINE

DEFINE CLASS Frm2 As Form
	Caption = "Form2"
	AutoCenter = .T.
	oCmdExit1 = NULL
	ADD OBJECT cmdExit As CommandButton WITH ;
		Caption = "Cancel",;
		Top = 5
	PROCEDURE cmdExit.Click()
		ThisForm.Release() 
	ENDPROC	 
	PROCEDURE Destroy()
		ACTIVATE SCREEN 
		?"Destroy: "+ThisForm.Caption  
		#IF USE_CLEARPROPS
			*-- Release property to external ref
			this.oCmdExit1 = NULL
		#ENDIF	 
		#IF USE_REMOVECHILDOBJS
			*-- Release object, the reference on which 
			*-- can be used by external objects
			this.RemoveObject('cmdExit') 
		#ENDIF	 
	ENDPROC	 
ENDDEFINE

PROCEDURE cleanup
ACTIVATE SCREEN 
?"Call Cleanup" 
IF Type('goFrm1') = 'O' AND !IsNull(goFrm1)
	#IF USE_RELEASE
		goFrm1.Release()
	#ENDIF	
	RELEASE goFrm1
ENDIF	
IF Type('goFrm2') = 'O' AND !IsNull(goFrm2)
	#IF USE_RELEASE
		goFrm2.Release()
	#ENDIF	
	RELEASE goFrm2
ENDIF
*/////////////////////////////// the end of any.prg
Самый радикальный способ заключается в использовании явных удалений встроенных объектов с помощью метода RemoveObject() класса-контейнера (USE_REMOVECHILDOBJS .T.). Однако, к нему не следует прибегать без крайней необходимости.
 
 
Hosted by uCoz