初始化数据结构或对象时,如果子对象在使用后需要显式释放过程,那么在初始化过程中如何处理错误?
让我举个例子,用SUBOBJ1和SUBOBJ2槽初始化一个OBJECT对象,设置指向int值的外部指针:
(defun init-object ()
(let ((obj (make-object)))
(setf (subobj1 obj) (cffi:foreign-alloc :int)
(subobj2 obj) (cffi:foreign-alloc :int))
obj))
Run Code Online (Sandbox Code Playgroud)
如果我们在SUBEJ2插槽的FOREIGN-ALLOCing中出错,我们应该为SUBOBJ1插槽执行FOREIGN-FREEing以避免内存泄漏.
作为一个想法,我可以写如下:
(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))
Run Code Online (Sandbox Code Playgroud)
你有更好的想法,或一般惯用的模式?
谢谢
在答案之后,我使用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))))))
Run Code Online (Sandbox Code Playgroud)
使用UNWIND-PROTECT.当错误导致退出范围时,unwind-protect
允许您强制执行清理表单.
像这样的东西:
(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))
Run Code Online (Sandbox Code Playgroud)
使用可用的任何内容来检测插槽是否绑定.以上假设未初始化的槽的值为NIL
.
Common Lisp 具有与当今语言(例如 Java、C#)异常和资源管理语句相对应的功能,例如try
withcatch
和/或finally
。
Common Lisp 中的-是用 实现的try
,就像您在代码中一样。可以简单地将相同的错误重新发回,但您将无法在实际发生的调试器上捕获该错误。Java 在异常创建时包含异常的堆栈跟踪。C# 包含抛出异常时的堆栈跟踪。无论如何,我认为两者都有方法抛出带有内部异常的新异常,因此您可以访问原始堆栈跟踪。catch
handler-case
Common Lisp 中的 - 是通过以下方式实现try
的finally
unwind-protect
。第一个表单正常执行,其余的无论第一个表单是否正常返回都无条件执行。
Common Lisp 有一个功能,允许在发出错误信号的位置运行代码,即handler-bind
. 主要区别在于handler-case
,它不会倒回堆栈,并且如果没有处理程序非本地退出,也不会阻止错误弹出到其他处理程序或调试器。
因此,你会使用这样的东西:
(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))
Run Code Online (Sandbox Code Playgroud)
我建议您不要匹配condition
,因为所有条件都继承自它,例如storage-condition
. 在无法或不可能恢复的情况下,您可能不想做任何事情。
仅供参考,Common Lisp 中的完整try
- catch
-子句是通过around实现的:finally
unwind-protect
handler-case
(unwind-protect
(handler-case
(do-something)
(error-type-1 ()
(foo))
(error-type-2 (e)
(bar e)))
(cleanup-form-1)
(cleanup-form-2))
Run Code Online (Sandbox Code Playgroud)