使用显式资源管理初始化数据结构时的适当错误处理?

mas*_*agi 5 common-lisp

初始化数据结构或对象时,如果子对象在使用后需要显式释放过程,那么在初始化过程中如何处理错误?

让我举个例子,用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)

Rai*_*wig 6

使用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.


ace*_*ent 3

Common Lisp 具有与当今语言(例如 Java、C#)异常和资源管理语句相对应的功能,例如trywithcatch和/或finally

Common Lisp 中的-是用 实现的try,就像您在代码中一样。可以简单地将相同的错误重新发回,但您将无法在实际发生的调试器上捕获该错误。Java 在异常创建时包含异常的堆栈跟踪。C# 包含抛出异常时的堆栈跟踪。无论如何,我认为两者都有方法抛出带有内部异常的新异常,因此您可以访问原始堆栈跟踪。catchhandler-case

Common Lisp 中的 - 是通过以下方式实现tryfinallyunwind-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实现的:finallyunwind-protecthandler-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)