5

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

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

如果我们在 SUBOBJ2 插槽的 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))

你有什么更好的主意,或者通常是惯用的模式吗?

谢谢


在回答之后,我使用 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))))))
4

4 回答 4

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))

使用任何可用的方法来检测插槽是否已绑定。上面假设一个未初始化的插槽的值为NIL

于 2012-11-28T12:49:35.317 回答
3

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

正如您在代码中所做的那样,Common Lisp 中的try-是通过 实现的。可以简单地重新发送相同的错误信号,但您不会在实际发生的调试器上捕获错误。Java 在创建异常时包含异常的堆栈跟踪。C# 包括抛出异常时的堆栈跟踪。无论如何,我认为两者都有办法用内部异常抛出新异常,因此您可以访问原始堆栈跟踪。catchhandler-case

-in Common Lisp 是用try. 第一个表单正常执行,其余的不管第一个表单是否正常返回都无条件执行。finallyunwind-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))

我建议您不要与 匹配condition,因为所有条件都继承自它,例如storage-condition. 在无法或不可能恢复的情况下,您可能不想做任何事情。


仅供参考,Common Lisp 中完整的--try子句是通过around实现的:catchfinallyunwind-protecthandler-case

(unwind-protect
     (handler-case
         (do-something)
       (error-type-1 ()
         (foo))
       (error-type-2 (e)
         (bar e)))
  (cleanup-form-1)
  (cleanup-form-2))
于 2012-11-29T14:52:42.743 回答
2

有人建议使用UNWIND-PROTECT. 这是处理资源分配的惯用方式。但是,如果您的目标是在出错时释放资源,但如果一切成功则返回这些资源,您可以使用类似以下的内容:

(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 处理,您将泄漏资源。

这样做的方法是在使用该函数时始终执行以下操作:

(let ((obj (init-object)))
  (unwind-protect
      ... use object here ...
    (free-object obj)))

另一种解决方案是在对象被 GC 时释放它。没有标准的做法,但必要的功能被抽象在TRIVIAL-GARBAGE:FINALIZE函数中。

于 2012-11-29T11:19:44.913 回答
0

我同意 Rainer 的建议:我将一个unwind-protect表单包装在一个宏中,并检查受保护子句中的初始化是否成功。

于 2012-11-28T22:06:08.940 回答