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

3

Common Lisp には、今日の言語 (Java、C# など) の例外およびリソース管理ステートメント ( trywithcatchおよび/またはなど) に対応する機能がありfinallyます。

try- catchin Common Lisp は、コードにあるように で実現されhandler-caseます。同じエラーを単純に再通知することは可能ですが、実際に発生したデバッガーでエラーをキャッチすることはできません。Java は、例外の作成時に例外のスタック トレースを含めます。C# には、例外がスローされたときに例外のスタック トレースが含まれます。いずれにせよ、どちらも内部例外で新しい例外をスローする方法があると思うので、元のスタックトレースにアクセスできます。

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

もう 1 つの解決策は、オブジェクトが GC されているときにオブジェクトを解放することです。標準的な方法はありませんが、必要な機能は関数に抽象化されていTRIVIAL-GARBAGE:FINALIZEます。

于 2012-11-29T11:19:44.913 に答える
0

Rainer の提案に 2 番目に同意します。unwind-protectフォームをマクロでラップし、保護された句で初期化が成功したかどうかを確認します。

于 2012-11-28T22:06:08.940 に答える