Goでは、2つの非nil関数ポインターを比較して同等性をテストする方法はありますか?私の平等の基準はポインターの平等です。そうでない場合、ポインタの同等性が許可されない特別な理由はありますか?
今のところ、これを簡単な方法で行おうとすると、次のようになります。
package main
import "fmt"
func SomeFun() {
}
func main() {
fmt.Println(SomeFun == SomeFun)
}
私は得る
./func-pointers.go:12: invalid operation: SomeFun == SomeFun (func can only be compared to nil)
この振る舞いが最近導入されたと私は理解しています。
リフレクトパッケージを使用して答えを見つけました。ただし、Atomは、これが実際には未定義の動作を生成することを以下に示しています。詳細および可能な代替ソリューションについては、Atomの投稿を参照してください。
package main
import "fmt"
import "reflect"
func SomeFun() { }
func AnotherFun() { }
func main() {
sf1 := reflect.ValueOf(SomeFun)
sf2 := reflect.ValueOf(SomeFun)
fmt.Println(sf1.Pointer() == sf2.Pointer())
af1 := reflect.ValueOf(AnotherFun)
fmt.Println(sf1.Pointer() == af1.Pointer())
}
出力:
true
false
平等とアイデンティティには違いがあることに注意してください。 Go1の演算子==
と!=
は、同一性ではなく、同等性(チャネルを比較する場合を除く)の値を比較しています。これらの演算子はnotで平等と同一性を混合しようとしているため、この点でGo1はpre-Go1よりも一貫性があります。
関数の同等性は関数の同一性とは異なります。
関数タイプで==
および!=
を許可しない理由の1つは、パフォーマンスです。たとえば、次のクロージャは、その環境の変数を使用していません。
f := func(){fmt.Println("foo")}
関数の比較を禁止すると、コンパイラーは、実行時に(実行時に)新しいクロージャーを作成する必要がなく、クロージャーの単一の実装を生成できます。したがって、パフォーマンスの観点から、関数の比較を許可しないという決定は良い決定でした。
reflect
パッケージを使用して関数のアイデンティティを決定することに関連して、次のようなコード
func SomeFun() {}
func AnotherFun() {}
func main() {
sf1 := reflect.ValueOf(SomeFun)
sf2 := reflect.ValueOf(SomeFun)
fmt.Println(sf1.Pointer() == sf2.Pointer()) // Prints true
af1 := reflect.ValueOf(AnotherFun)
fmt.Println(sf1.Pointer() == af1.Pointer()) // Prints false
}
未定義の動作に依存します。プログラムが何を印刷するかについての保証はありません。コンパイラーは、SomeFun
とAnotherFun
を単一の実装にマージすることを決定する場合があります。その場合、2番目のprintステートメントはtrue
を出力します。実際、最初のprintステートメントがtrue
を出力するという保証はまったくありません(他のGo1コンパイラーおよびランタイムでは、false
を出力する場合があります)。
元の質問に対する正解は次のとおりです。
package main
import "fmt"
func F1() {}
func F2() {}
var F1_ID = F1 // Create a *unique* variable for F1
var F2_ID = F2 // Create a *unique* variable for F2
func main() {
f1 := &F1_ID // Take the address of F1_ID
f2 := &F2_ID // Take the address of F2_ID
// Compare pointers
fmt.Println(f1 == f1) // Prints true
fmt.Println(f1 == f2) // Prints false
}
回避策は状況によって異なります。関数を比較していた場所をいくつか変更する必要がありました。ある場合、私は何か違うことをしたので、もうそれらを比較する必要はありません。別のケースでは、構造体を使用して、関数を同等の文字列に関連付けました。
type nameFunc struct {
name string
fval func()
}
比較する必要のある関数が2つしかないため、これらの構造体のスライスを保持し、必要に応じてスライスをスキャンして、名前フィールドを比較し、fvalをディスパッチするのが最も簡単でした。非常に多い場合は、代わりにマップを使用できます。関数のシグネチャが異なる場合は、インターフェイスなどを使用できます。
Go 1プランに従って、マップと関数の値の比較は許可されなくなりました(nilとの比較を除く)。関数の同等性は一部のコンテキストで問題があり、マップの同等性はマップのコンテンツではなくポインターを比較します。
関数の同等性は、クロージャが存在する場合に問題がありました(2つのクロージャが等しいのはいつですか?)