質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

プログラミング言語

プログラミング言語はパソコン上で実行することができるソースコードを記述する為に扱う言語の総称です。

ポインタ

ポインタはアドレスを用いてメモリに格納された値を"参照する"変数です。

Q&A

解決済

1回答

722閲覧

Go言語でのメソッドのレシーバの型: 値 or ポインタ

sin_250

総合スコア112

Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

プログラミング言語

プログラミング言語はパソコン上で実行することができるソースコードを記述する為に扱う言語の総称です。

ポインタ

ポインタはアドレスを用いてメモリに格納された値を"参照する"変数です。

0グッド

0クリップ

投稿2020/01/12 10:26

Go言語での構造体のメソッドのレシーバ型を値にすべきかポインタにすべきかの一般的なベストプラクティスは存在しますでしょうか。
色々と調べてみたのですが、諸説あるようで困っています。

go

1/* 例(少し不適切な例かも) */ 2type Hoge struct { 3 data int 4} 5 6func (h Hoge) getIncremented() int { 7 return h.data + 1 8} 9 10func (h *Hoge) increment() { 11 h.data += 1 12}

調べていると、特に理由がなければポインタ型のほうを使うという意見が多いようです。
理由は以下。

  • ポインタ型の場合、コピーが発生しない
  • 構造体のメソッドは構造体のデータと紐付いた処理が多いので(データを直接変更できる)ポインタ型を普段使いするのが自然だ

一方でポインタ型を使うのは構造体のデータを直接変更する真の理由があるときに限り、それ以外は値型を使うべきだという意見も見かけます。理由は以下。

  • Goは仮引数にconstをつけられない。データを変更しない場合は値型にしておけば元のデータが書き換わらないことを保証できる
  • 冗長なコピーの計算コストなんて知れてる程度だから気にしないでいい(!?)

C++を使っていると、クラスや構造体をread-onlyで引数に取る場合、const付き参照(const Hoge&)で受けるのが一般的(これによってコピーが発生しないので効率が良く、かつデータが書き換わらないことも保証できるから)だと思いますが、Goでは何がベストプラクティスなのでしょうか。

よろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

変更を許さない作りはフィールドをプライベートにしてGetXXXなメソッドだけを提供することでできます。

変更を許さないために値型構造体をわざわざ使うという事例はレアケースかなと思います。

私の判断基準は構造体フィールドセットが有限である事が確実な場合のみ値型を利用します。それ以外はポインタ型です。
なので、ほとんどのケースでポインタ型を利用します。

私の考える例外は以下の様な場合です。
スライスと異なり配列は非参照型で代入や引数渡しの際に常にコピーが走ります。
https://play.golang.org/p/1yXLAyZiMct

go

1package main 2 3import ( 4 "fmt" 5) 6 7type Vector2D [2]float32 8 9func (v Vector2D) Add(n Vector2D) Vector2D { 10 return Vector2D{n[0] + v[0], n[1] + v[1]} 11} 12 13func main() { 14 v1 := Vector2D{1.2, 2.4} 15 v2 := Vector2D{2.2, 4.4} 16 fmt.Println(v1.Add(v2)) 17}

投稿2020/01/13 02:18

編集2020/01/13 17:39
nobonobo

総合スコア3367

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sin_250

2020/01/13 03:50

ご回答有り難うございます。すみません、もう少し詳しく教えてください。 >構造体フィールドセットが有限である事が確実な場合 これは、以下のようなサイズが固定の構造体(配列(文字列含む)などを持たない構造体)という意味でしょうか? type Hoge struct { Id int // size fixed Age int // size fixed // Name string // size is not fixed // Address string // size is not fixed }
nobonobo

2020/01/13 17:46 編集

スライス、マップの値の実体は「可変長メモリへのポインタ」と「サイズ」と「キャパシティ」の3フィールドの構造体です。文字列の実体は「ポインタ」と「サイズ」の2フィールドの構造体です。 なので要素の長さに依らずサイズは固定です。 なので、コピーされるのは固定長フィールドだけで、可変長要素は複製されることなく同じ値が参照されます。 これらは直接にポインタ型ではないけれど要素はポインタ参照してるので、扱いはポインタ型と同じ扱いで検討すれば良いと思います。固定長部分だけコピーされるのでコピーコストに関しては固定長構造体とみなして大丈夫です。
nobonobo

2020/01/13 17:16

配列(スライスではない)だけはサイズ分を含む固定長構造体になりますが、わざわざ配列を宣言する事は将来サイズ変更しないという事なので非ポインタのまま扱う事を検討しても良いかもしれません。
nobonobo

2020/01/13 17:24

有限なフィールドセットの例は ベクトルやクォータニオンなど要素の数や型が固定かつメモリサイズが一定で、将来変更する事がない様なものですね。
nobonobo

2020/01/13 17:51

噛み砕いていうと、構造定義の各要素を利用する関数にバラバラの引数で渡す形にしても将来困らないような構造体のみレシーバを値型にします。
sin_250

2020/01/15 13:41

詳細なご回答有り難うございます。 すべてを理解できたわけではございませんが、色々と調べるヒントをいただけたと思います。 引き続き勉強します。ありがとうございました。
nobonobo

2020/01/15 23:57

名前や住所を入れた構造の場合、将来EMailアドレスなどを追加する可能性があるのでポインタ型レシーバーにします。(学術的に不変でない概念は変化する可能性ありと考えて良いと思います。)
sin_250

2020/01/17 11:48

コメントありがとうございます。すみません、やはりあまり理解できていないようです。 (> _ <) 構造体のメンバ変数が将来増えたり減ったり変化することと、ポインタ型or値型レシーバの選択との関連が理解できておりません。 おっしゃっている例の理由は、将来どんどんメンバ変数が増えていってしまったときにサイズ肥大化に伴ってコピーコストが許容できなくなる可能性がある、ということでしょうか? それとも、パフォーマンスの問題だけでなく、値型レシーバを使っているメソッドはメンバ変数が変化した時に壊れたりコードのメンテ性に問題が出るようなことがあるということでしょうか・・・?
nobonobo

2020/01/18 09:03

複数の構造体をスライスに入れる場合、rangeで全ての値にアクセスする際、やはり値型とポインタ型とでのコピーコストの差はそれなりに影響が大きいです。 将来構造を拡張する可能性がある場合、値型の特性を維持し続けることが難しいからです。例えば、構造体フィールドにスライスやマップ、ポインタ型構造などの参照タイプが入る事になった時点でそれは値型としてのメリットである独立性は失われて、扱いが参照型と同じくナイーブになります。(複数のゴルーチンがアクセスする場合に排他処理が必要になります。 (もちろん将来の拡張に一定のルールを設けてスライス、マップ、ポインタ型を絶対に増やさないとするならアリかと) また、レシーバに値型とポインタ型が両方あるとその構造体をインターフェース型として扱うことができません。どちらか一方に統一する必要があります。
sin_250

2020/01/26 04:27

>将来構造を拡張する可能性がある場合、値型の特性を維持し続けることが難しいからです。例えば、構造体フィールドにスライスやマップ、ポインタ型構造などの参照タイプが入る事になった時点でそれは値型としてのメリットである独立性は失われて、扱いが参照型と同じくナイーブになります お返事が遅れてしまい申し訳ありません、上記、納得できました。ありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問