5

この記事には、「defer ステートメントは関数呼び出しをリストにプッシュする」と記載されています。プログラムの別の場所からそのリストの要素にアクセスして、それらを呼び出すことができるかどうか疑問に思っていますか? それらを複数回呼び出すことはできますか? 遅延動作を持つ関数への参照があると仮定しています(それが役立つ場合)。

だから、ここに私がやりたいことの短い例があります:

func main {
    doStuff = func() {
        // open database connections
        // write temporary files
        // etc...

        defer func() {
            // close database connections
            // delete temporary files
            // etc...
        }()
    }

    AwesomeApplication(doStuff)
}

func AwesomeApplication(doStuff func()) {
    // Now, can I get a reference to the defer function within `doStuff`?
    // No, I can't just define the defer function somewhere an pass it
    // with `doStuff`.  Think of this as a curiosity I want to satisfy,
    // not a real use case.
}
4

1 に答える 1

11

deferto呼び出しが保存される「リスト」は完全に実装固有であるため、このリストにアクセスする信頼できる方法はありません1 , 2 *g コンパイラ ファミリーの実装の詳細 (少し古いですが) は、Russ Cox の研究ブログ にあります。

遅延関数は現在のゴルーチン ( ) に関連付けられており、g->Defer(*g ファミリーの場合) 現在のスタック ポインターによって識別されます。現在のスタック フレームが最上位のエントリに格納されているスタック フレームと一致する場合Defer、この関数が呼び出されます。

この知識あれば、cgo を使用して遅延関数のリストにアクセスできます。知っておく必要があります

  • 現在のスタック ポインタ
  • 関数のアドレス
  • 現在のゴルーチン

ただし、これを使用することはお勧めしません。説明しているユースケースの一般的な解決策は、次のような機能を持つことです。

func setupRoutines() (setUp, tearDown func()) {
    // store db connection object and such

    return func() { /* connect db and such */ }, func() { /* close db and such */ }
}

tearDownコード内で、 を使用して呼び出される関数を共有できますdefer。このようにして、すべてのデータベース接続とそのようなローカルを持つというボーナスがまだありますが、初期化/切断機能を共有できます。

遊ぶためのフィドル

と Cをいじってunsafeみたい場合は、次のコードをテンプレートとして使用できます。

inspect/runtime.c:

// +build gc
#include <runtime.h>

void ·FirstDeferred(void* foo) {
    foo = g->defer->fn;

    FLUSH(&foo);
}

inspect/inspect.go

package inspect

import "unsafe"

func FirstDeferred() unsafe.Pointer

defer.go

package main

import "defer/inspect"

func f(a, b int) {
    println("deferred f(", a, b, ")")
}

func main() {
    defer f(1, 2)
    println( inspect.FirstDeferred() )
}

このコード (これに基づく) は、現在の go ルーチン ( g)にアクセスできるため、そのdefer属性にアクセスできます。FuncValしたがって、関数へのポインターにアクセスし、それをラップして返すことができるはずです。

于 2013-08-26T23:40:56.407 に答える