5

私は 9 年間の C++ 開発を経て Go を探求しています。C++ では、組み込み型の変数を除いて、関数の引数を値で渡すのはパフォーマンスの低下のため悪い習慣です。引数のすべてのフィールドがコピーされ、ほとんどの場合、非常にコストのかかる操作になります。

これはGoにも当てはまりますか?「const」セマンティックをメソッドに割り当てるためだけに「this」を値で渡すのは非常にコストがかかります。Goコンパイラは、最初の変更の前に変数がコピーされるのを防ぐのに十分スマートですか? C/C++ のように Go で「this」を値渡しするのがアンチパターンではないのはなぜですか?

4

4 に答える 4

10

他の答えは良いですが、私の意見では、いくつかの情報が不足しています。

次のコードで示されているように、Goのレシーバーは単なる構文糖衣です。

package main

import "fmt"

type Something struct {
    Value int
}

func (s *Something) ChangeValue(n int) {
    s.Value = n
}

func main() {
    o := new(Something)             // o is of type *Something
    fmt.Println(o.Value)            // Prints 0
    o.ChangeValue(8)                // Changes o.Value to 8
    fmt.Println(o.Value)            // Prints 8
    (*Something).ChangeValue(o, 16) // Same as calling o.ChangeValue(16)
    fmt.Println(o.Value)            // Prints 16
}

これに基づいて、のレシーバーが1へのポインターではなくChangeValuetypeの値である場合にどうなるかを考えてください...Something

それは正しい!oこのメソッドを使用して、のValueフィールドを実際に変更することはできません。ほとんどの場合、カプセル化を行うためにポインターレシーバーを使用します。

于 2013-03-10T23:07:49.180 に答える
6

Goの「this」はレシーバーと呼ばれます。はい、「const」セマンティクスをエミュレートするためだけに非ポインターレシーバーを使用することは非常にコストがかかる場合があります。しかし、Goは正当な理由で「const」修飾子を削除しました。したがって、不必要なコピーを犠牲にして、その特定の言語設計の決定を引き継ぐことはおそらく良い考えではありません-少数の機械語よりも大きいものの場合。

ところで、「this」または「self」と「receiver」の用語の違いは、セマンティクスも異なることを意味します。IIRCでは、他の言語では「this」または「self」の値を変更できませんが、Goでは、レシーバーは単なる別の関数パラメーターです(実際にはコンパイラーの観点からは最初のパラメーターです)。

そうは言っても、これが、レシーバー変数の名前がthisorであるメソッドの記述を推奨しない理由selfです。他の言語に慣れている人にとっては誤解を招く恐れがあります。

うまくいけばアイデアを説明する完全に作り上げられた例:

func (n *node) walk(f func(*node)) {
        for n != nil {
                f(n)
                n = n.next
        }
}
于 2013-03-10T19:10:02.707 に答える
5

受信機のサイズによって異なります。レシーバーが数十バイト未満の場合、ポインターを渡した場合に必要となるポインターの追跡 (追加のメモリー アクセス) よりも、実際にはコピーの方がコストがかからない可能性があります。また、ポインターを使用すると、構造体がヒープに割り当てられる可能性が高くなり、ガベージ コレクターに余分な負担がかかります。

Go では、コピーは常にバイト単位のコピーであるため、コストは構造体のサイズのみに依存します。C++ では、コピー コンストラクターを呼び出す可能性があり、これには多くの時間がかかる可能性があります。

したがって、非常に大きなオブジェクトを除いて、メソッドのセマンティクスと API の残りの部分との一貫性に基づいて最も理にかなっている種類のレシーバーを使用してください。

于 2013-03-11T15:54:45.683 に答える
5

あなたの C++ の知識は、関数の引数として高価なもの (構造体を値で渡す) とそうでないもの (int などの組み込み型) について、Go にうまく変換できると思います。

主な違いは、参照タイプ、スライス、mapおよびchannelです。これらは、値によって渡されるように見えますが (ポインターを使用する必要はありません)、実際には参照によって渡されるため、通常はスライス、マップ、またはチャネルへのポインターを使用しないでください。

strings も特別です。内部では参照型ですが、不変でもあるため、直接渡します。

Go で呼び出されるレシーバーの特定のケースについては、this同じルールが適用されます (C++ とは異なり、組み込み型をレシーバーとして使用できることに注意してください)。コンパイラはコピーを回避するほどスマートではないと思います。大きな構造体にはポインターを使用します。

于 2013-03-10T19:21:37.773 に答える