🔥 값 또는 포인터 리시버 선택하기

361자
5분

Go 언어에서 메서드의 리시버로 값(value) 또는 포인터(pointer)를 사용하는 데에는 두 가지 이유가 있습니다.

첫 번째 이유는 메서드가 리시버가 가리키는 값을 수정할 수 있도록 하기 위함입니다. 다음 예제 코드를 살펴보겠습니다.

func (v *Vertex) Scale(f float64) {
    // 포인터 리시버를 사용하여 Vertex 구조체의 필드 값을 수정합니다.
    v.X = v.X * f
    v.Y = v.Y * f
}
 
go

Scale 메서드는 포인터 리시버를 사용하여 Vertex 구조체의 XY 필드 값을 수정합니다. 만약 값 리시버를 사용한다면, 메서드 내에서 수정한 값은 메서드 호출이 끝난 후에 사라지게 됩니다.

두 번째 이유는 메서드 호출 시마다 값을 복사하는 것을 피하기 위함입니다. 예를 들어, 리시버가 큰 구조체인 경우에는 값 복사로 인한 오버헤드를 줄일 수 있습니다.

func (v *Vertex) Abs() float64 {
    // 값을 수정할 필요는 없지만, 포인터 리시버를 사용하여 값 복사를 피합니다.
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
 
go

Abs 메서드는 리시버의 값을 수정할 필요가 없지만, 포인터 리시버를 사용하여 값 복사를 피하고 있습니다.

일반적으로 한 타입의 모든 메서드는 값 또는 포인터 리시버 중 하나만 사용하는 것이 좋습니다. 값과 포인터 리시버를 혼용하는 것은 혼란을 야기할 수 있기 때문입니다.

다음은 위 예제 코드의 전체 내용입니다.

package main
 
import (
    "fmt"
    "math"
)
 
type Vertex struct {
    X, Y float64
}
 
func (v *Vertex) Scale(f float64) {
    // 포인터 리시버를 사용하여 Vertex 구조체의 필드 값을 수정합니다.
    v.X = v.X * f
    v.Y = v.Y * f
}
 
func (v *Vertex) Abs() float64 {
    // 값을 수정할 필요는 없지만, 포인터 리시버를 사용하여 값 복사를 피합니다.
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
 
func main() {
    v := &Vertex{3, 4}
    fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
    // 크기 조정 전 Vertex 값과 Abs 결과를 출력합니다.
 
    v.Scale(5)
    // Scale 메서드를 호출하여 Vertex 값을 5배로 크기 조정합니다.
 
    fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
    // 크기 조정 후 Vertex 값과 Abs 결과를 출력합니다.
}
 
go

main 함수에서는 Vertex 구조체의 포인터를 생성하고, 크기 조정 전후의 값과 Abs 메서드의 결과를 출력합니다. Scale 메서드를 호출하여 VertexXY 값을 5배로 크기 조정한 후, 다시 한 번 값과 Abs 결과를 출력하여 변경 사항을 확인할 수 있습니다.

이 예제를 통해 값 또는 포인터 리시버를 선택하는 기준과 그 차이점을 이해할 수 있습니다. 메서드가 리시버의 값을 수정해야 하는 경우나 큰 구조체의 값 복사를 피하고자 할 때는 포인터 리시버를 사용하는 것이 효과적입니다.