Common lisp 使用显式资源管理初始化数据结构时是否进行了适当的错误处理?
当初始化一个数据结构或对象时,它的子对象在使用后需要显式释放过程,我应该如何处理初始化过程中的错误 让我举一个例子,初始化SUBOBJ1和SUBOBJ2插槽的对象,将外部指针设置为int值:Common lisp 使用显式资源管理初始化数据结构时是否进行了适当的错误处理?,common-lisp,Common Lisp,当初始化一个数据结构或对象时,它的子对象在使用后需要显式释放过程,我应该如何处理初始化过程中的错误 让我举一个例子,初始化SUBOBJ1和SUBOBJ2插槽的对象,将外部指针设置为int值: (defun init-object () (let ((obj (make-object))) (setf (subobj1 obj) (cffi:foreign-alloc :int) (subobj2 obj) (cffi:foreign-alloc :int))
(defun init-object ()
(let ((obj (make-object)))
(setf (subobj1 obj) (cffi:foreign-alloc :int)
(subobj2 obj) (cffi:foreign-alloc :int))
obj))
如果我们在SUBOBJ2插槽的外部分配中出错,我们应该为SUBOBJ1插槽执行外部释放以避免内存泄漏
作为一个想法,我可以写如下:
(defun init-object ()
(let ((obj (make-object)))
(handler-case
(setf (subobj1 obj) (cffi:foreign-alloc :int)
(subobj2 obj) (cffi:foreign-alloc :int))
(condition (c) ; forcedly handling all conditions
(when (subobj1 obj) (cffi:foreign-free (subobj1 obj)))
(error c))) ; invoke the condition c again explicitly
obj))
你有没有更好的想法,或者通常的惯用模式
谢谢
在回答之后,我添加了一个使用UNWIND-PROTECT的代码。这将不起作用,因为即使成功完成所有分配,也会运行释放表单
(defun init-object ()
(let ((obj (make-object)))
(unwind-protect
(progn
(setf (subobj1 obj) (cffi:foreign-alloc :int)
(subobj2 obj) (cffi:foreign-alloc :int))
obj)
; foreign pointers freed even when successfully initialized
(when (subobj2 obj) (cffi:foreign-free (subobj2 obj)))
(when (subobj1 obj) (cffi:foreign-free (subobj1 obj))))))
使用。当错误导致退出范围时,解除保护
允许您强制执行清理表单
大概是这样的:
(defun init-object ()
(let ((obj (make-object)))
(unwind-protect
(setf (subobj1 obj) (cffi:foreign-alloc :int)
(subobj2 obj) (cffi:foreign-alloc :int))
(unless (and (subobj2 obj) (subobj1 obj))
(when (subobj1 obj) (cffi:foreign-free (subobj1 obj)))
(when (subobj2 obj) (cffi:foreign-free (subobj2 obj)))))
obj))
(defun init-object ()
(let ((obj (make-object)))
(handler-bind
(;; forcedly handling all conditions
(condition #'(lambda (c)
(declare (ignore c))
(when (subobj1 obj) (cffi:foreign-free (subobj1 obj)))
;; return normally, allowing the condition to go up the handler chain
;; and possibly to the debugger, if none exits non-locally
)))
(setf (subobj1 obj) (cffi:foreign-alloc :int)
(subobj2 obj) (cffi:foreign-alloc :int)))
obj))
使用任何可用的方法来检测插槽是否已绑定。上面假设未初始化的插槽的值为
NIL
I第二个Rainer建议:
我将
展开保护
表单包装在宏中,并检查protected子句中的初始化是否成功。有人建议使用展开保护
。这是处理资源分配的惯用方法。但是,如果您的目标是在发生错误时释放资源,但如果所有操作都成功,则返回这些资源,则可以使用类似以下的方法:
(defun init-object ()
(let ((obj (create-object)))
(handler-case
(progn
(setf (subobj1 obj) (cffi:foreign-alloc :int))
(setf (subobj2 obj) (cffi:foreign-alloc :int))
obj)
(error (condition)
(free-object obj)
;; Re-throw the error up in the call chain
(error condition)))))
(defun free-object (obj)
(when (subobj2 obj) (cffi:foreign-free (subobj2 obj)))
(when (subobj1 obj) (cffi:foreign-free (subobj1 obj))))
实现相同功能的另一种方法是进行检查,以验证是否已达到函数的结尾,如果未达到,则释放对象。然而,我并不真的喜欢这种风格,因为它并不能很好地展示正在发生的事情
但是,请注意,当您使用函数INIT-OBJECT
时,需要将其包含在UNWIND-PROTECT
中。否则,一旦函数返回的对象被GC'ed,您将泄漏资源
执行此操作的方法是在使用此功能时始终执行以下操作:
(let ((obj (init-object)))
(unwind-protect
... use object here ...
(free-object obj)))
另一个解决方案是在对象被GC’ed时释放它。没有标准的方法来做这件事,但是必要的功能被抽象在
trial-GARBAGE:FINALIZE
函数中。Common Lisp具有与当今语言(如Java、C#)异常和资源管理语句相对应的功能,例如,使用catch
和/或finally
尝试
Common Lisp中的try
-catch
是通过实现的,正如您在代码中所做的那样。可以简单地重新标记相同的错误,但是您不会在实际发生错误的调试器上捕获错误。Java在创建异常时包含异常的堆栈跟踪。C#包括抛出异常时异常的堆栈跟踪。在任何情况下,我认为这两种方法都可以抛出一个新异常和一个内部异常,这样您就可以获得原始的stacktrace
公共Lisp中的try
-最后
是用实现的。第一个表单正常执行,其余的无条件执行,不管第一个表单是否正常返回
CommonLisp有一个功能,允许在发出错误信号的点运行代码。关于处理程序case
的主要区别在于,如果没有处理程序非本地退出,它不会倒带堆栈,也不会阻止错误弹出到其他处理程序或调试器
因此,您可以使用如下内容:
(defun init-object ()
(let ((obj (make-object)))
(unwind-protect
(setf (subobj1 obj) (cffi:foreign-alloc :int)
(subobj2 obj) (cffi:foreign-alloc :int))
(unless (and (subobj2 obj) (subobj1 obj))
(when (subobj1 obj) (cffi:foreign-free (subobj1 obj)))
(when (subobj2 obj) (cffi:foreign-free (subobj2 obj)))))
obj))
(defun init-object ()
(let ((obj (make-object)))
(handler-bind
(;; forcedly handling all conditions
(condition #'(lambda (c)
(declare (ignore c))
(when (subobj1 obj) (cffi:foreign-free (subobj1 obj)))
;; return normally, allowing the condition to go up the handler chain
;; and possibly to the debugger, if none exits non-locally
)))
(setf (subobj1 obj) (cffi:foreign-alloc :int)
(subobj2 obj) (cffi:foreign-alloc :int)))
obj))
我建议您不要匹配条件
,因为所有条件都继承自它,例如存储条件
。你可能不想在你无法或不可能恢复的情况下做任何事情
仅供参考,Common Lisp中的完整try
-catch
-finally
子句是通过展开保护
在处理程序案例周围实现的:
(unwind-protect
(handler-case
(do-something)
(error-type-1 ()
(foo))
(error-type-2 (e)
(bar e)))
(cleanup-form-1)
(cleanup-form-2))
在这种情况下,UNWIND-PROTECT不适用,因为成功分配的外部分配值必须在init对象之后使用。对于释放对象,或者释放对象,UNWIND-PROTECT工作得很好。如果init成功,我会检查UNWIND-PROTECT protected子句,如果不成功,则取消分配所有分配的内容。如果代码发出适当错误的信号(在所有情况下),那么编写处理程序就可以了。如果你想要一些奢侈的东西,你可以写一个宏(带有checked foreign allocation(obj…),它提供了一个语言结构。我可以问更多的细节吗?我在问题中添加了一个使用UNWIND-PROTECT的代码。你的意思是这样的吗?谢谢。你的第一种方法和我的相同。你的第二种方法是我刚才要尝试的,效果很好(但是,正如您所说,由于检查是否已到达函数的末尾,因此样式不是很好)。当然,我附上了代码,其中分配的对象用于展开保护。没问题。关于琐碎垃圾:FINALIZE,我认为这是另一种好方法。我对这个话题太紧张了吗?我不这么认为。这是一件值得思考的事,因为它有助于您了解对象的生命周期。一旦您对它感到满意,您就会能够编写这种代码而不需要考虑太多。好的,我认为应该严格考虑对象的生命周期,所以我想制作一些模式来应用于这种情况。谢谢,HANDLER-BIND是我想要的。我将检查它的详细描述,并与条件匹配使用它。只有在某些情况下才会评估清理表单发生错误时,即使如此,条件层叠过程也不会发生prevented@masayukitakagi正是如此。handler case
的CLHS条目中的注释显示了使用handler bind
的等效实现,其中控制首先非本地传输,从而防止信号进入处理程序链,r