매개 변수 및 리턴 값의 포인터 대 값
Go에는 struct
값이나 슬라이스 를 반환하는 다양한 방법이 있습니다 . 내가 본 사람들을 위해 :
type MyStruct struct {
Val int
}
func myfunc() MyStruct {
return MyStruct{Val: 1}
}
func myfunc() *MyStruct {
return &MyStruct{}
}
func myfunc(s *MyStruct) {
s.Val = 1
}
나는 이것들의 차이점을 이해합니다. 첫 번째는 구조체의 복사본을 반환하고 두 번째는 함수 내에서 생성 된 구조체 값에 대한 포인터를 반환하고 세 번째는 기존 구조체가 전달되고 값을 재정의합니다.
이 모든 패턴이 다양한 상황에서 사용되는 것을 보았습니다. 이에 관한 모범 사례가 무엇인지 궁금합니다. 언제 사용합니까? 예를 들어, 첫 번째는 작은 구조체에 대해서는 괜찮을 수 있고 (오버 헤드가 최소이므로) 두 번째는 더 큰 구조체에 적합합니다. 그리고 세 번째는 매우 효율적인 메모리를 원한다면 호출간에 단일 구조체 인스턴스를 쉽게 재사용 할 수 있기 때문입니다. 사용시기에 대한 모범 사례가 있습니까?
마찬가지로 슬라이스에 관한 동일한 질문 :
func myfunc() []MyStruct {
return []MyStruct{ MyStruct{Val: 1} }
}
func myfunc() []*MyStruct {
return []MyStruct{ &MyStruct{Val: 1} }
}
func myfunc(s *[]MyStruct) {
*s = []MyStruct{ MyStruct{Val: 1} }
}
func myfunc(s *[]*MyStruct) {
*s = []MyStruct{ &MyStruct{Val: 1} }
}
여기서도 모범 사례는 무엇입니까? 슬라이스가 항상 포인터라는 것을 알고 있으므로 슬라이스에 대한 포인터를 반환하는 것은 유용하지 않습니다. 그러나 구조체 값 조각, 구조체 포인터 포인터를 조각으로 반환해야합니까? 슬라이스 포인터를 인수로 전달해야합니까 ( Go App Engine API 에서 사용되는 패턴 )?
tl; dr :
- 수신기 포인터를 사용하는 방법이 일반적입니다. 리시버의 경험 법칙은 "의심 스럽다면 포인터를 사용하십시오"입니다.
- 슬라이스, 맵, 채널, 문자열, 함수 값 및 인터페이스 값은 내부적으로 포인터로 구현되며 해당 포인터는 종종 중복됩니다.
- 다른 곳에서는 큰 구조체 또는 구조체에 포인터를 사용하고 변경해야하며 그렇지 않으면 값을 전달합니다 . 포인터를 통해 놀라운 일이 발생하면 혼란 스럽습니다.
포인터를 자주 사용해야하는 경우 :
- 수신자 는 다른 주장보다 더 자주 포인터입니다. 메소드가 호출 된 것을 수정하거나 명명 된 유형이 큰 구조체 가 되는 것은 드문 일이 아니므 로 드문 경우를 제외하고 지침이 포인터로 기본 설정됩니다.
- Jeff Hodges의 카피 파이터 도구는 가치가없는 비 수신 리시버를 자동으로 검색합니다.
- Jeff Hodges의 카피 파이터 도구는 가치가없는 비 수신 리시버를 자동으로 검색합니다.
포인터가 필요하지 않은 상황 :
코드 리뷰 가이드 라인이 통과하는 것이 좋습니다 작은 구조체를 같은
type Point struct { latitude, longitude float64 }
당신의 요구를 호출하고 함수가 장소에 수정할 수 있도록하지 않는 한 값으로, 어쩌면 조금 더 큰 심지어 일을합니다.- 값 의미론은 여기에 할당이 놀랍게도 값을 변경하는 앨리어싱 상황을 피합니다.
- 약간의 속도로 깔끔한 의미를 희생하는 것은 Go-y가 아니며 때로는 캐시 누락 이나 힙 할당을 피하기 때문에 값으로 작은 구조체를 전달하는 것이 실제로 더 효율적 입니다.
- 따라서 Go Wiki의 코드 검토 주석 페이지는 구조체가 작고 그 상태를 유지할 가능성이있을 때 가치를 전달하는 것을 제안합니다.
- "큰"컷오프가 모호해 보이는 경우입니다. 아마도 많은 구조체가 포인터 또는 값이 올바른 범위에 있습니다. 하한선으로, 코드 검토 의견은 슬라이스 (3 개의 기계어)가 가치 수신자로 사용하기에 합리적이라고 제안합니다. 상한에 가까울수록
bytes.Replace
10 단어의 args (3 조각 및int
)가 필요합니다.
들어 조각 , 당신은 배열의 변화 요소에 대한 포인터를 통과 할 필요가 없습니다. 예를 들어
io.Reader.Read(p []byte)
의 바이트를 변경합니다p
. 그것은 내부적으로 당신이라는 작은 구조의 주위에 통과하고 있기 때문에 ", 값 등의 치료 작은 구조체"틀림없이의 특별한 경우의 슬라이스 헤더 (볼 러스 콕스 (RSC)의 설명 ). 마찬가지로 지도 를 수정하거나 채널에서 통신 하기 위해 포인터가 필요하지 않습니다 .슬라이스의 경우 슬라이스 의 시작 / 길이 / 용량을 변경하여
append
슬라이스 값을 수락하고 새 값을 반환하는 것과 같은 내장 함수를 다시 만듭니다. 나는 그것을 모방 할 것이다. 앨리어싱을 피하고, 새로운 슬라이스를 반환하면 새로운 배열이 할당 될 수 있다는 사실에주의를 기울이고 호출자에게 친숙합니다.- 항상 그 패턴을 따르는 것은 아닙니다. 데이터베이스 인터페이스 또는 시리얼 라이저 와 같은 일부 도구 는 컴파일 타임에 해당 유형을 알 수없는 슬라이스에 추가해야합니다. 때로는
interface{}
매개 변수 에서 슬라이스에 대한 포인터를 허용합니다 .
- 항상 그 패턴을 따르는 것은 아닙니다. 데이터베이스 인터페이스 또는 시리얼 라이저 와 같은 일부 도구 는 컴파일 타임에 해당 유형을 알 수없는 슬라이스에 추가해야합니다. 때로는
슬라이스와 같은 맵, 채널, 문자열 및 함수 및 인터페이스 값 은 내부 참조 또는 이미 참조가 포함 된 구조이므로 기본 데이터를 복사하지 않으려는 경우 포인터를 전달할 필요가 없습니다. . (rsc 는 인터페이스 값이 저장되는 방법에 대한 별도의 게시물을 작성했습니다 ).
- 당신은 아직도 당신이 원하는 그 희소 한 케이스에 포인터를 전달해야 할 수 있습니다 수정 발신자의 구조체 :
flag.StringVar
소요*string
예를 들어, 그 이유.
- 당신은 아직도 당신이 원하는 그 희소 한 케이스에 포인터를 전달해야 할 수 있습니다 수정 발신자의 구조체 :
포인터를 사용하는 위치 :
함수가 포인터가 필요한 구조체의 메소드인지 여부를 고려하십시오. 사람들은 많은 방법으로
x
수정을 기대x
하므로 수신자가 수정 된 구조체를 만들면 놀라움을 최소화하는 데 도움이 될 수 있습니다. 수신자가 포인터가되어야하는 시점 에 대한 지침 이 있습니다.수신자가 아닌 매개 변수에 영향을 미치는 함수는 godoc 또는 godoc 및 이름 (예 :)에서이를 명확하게해야합니다
reader.WriteTo(writer)
.재사용을 허용하여 할당을 피하기 위해 포인터를 수락한다고 언급했습니다. 메모리 재사용을 위해 API를 변경하는 것은 할당이 사소한 비용이 들지 않을 때까지 지연되고 모든 사용자에게 까다로운 API를 강요하지 않는 방법을 찾고 있습니다.
- 할당을 피하기 위해 Go의 탈출 분석 은 친구입니다. 때로는 간단한 생성자, 일반 리터럴 또는 유용한 0 값으로 초기화 할 수있는 유형을 만들어 힙 할당을 피하는 데 도움이 될 수 있습니다
bytes.Buffer
. Reset()
일부 stdlib 유형이 제공하는 것처럼 오브젝트를 공백 상태로 되 돌리는 방법을 고려하십시오 . 할당을 신경 쓰지 않거나 저장할 수없는 사용자는 할당 할 필요가 없습니다.- 편의를 위해 다음 위치에서
existingUser.LoadFromJSON(json []byte) error
랩핑 될 수있는 위치에서 수정 메소드 및 스크래치에서 작성 기능을 일치하는 쌍 으로 작성하십시오NewUserFromJSON(json []byte) (*User, error)
. 다시 말하지만, 게으름과 곤란한 할당 사이의 선택을 개별 발신자에게 푸시합니다. - 메모리를 재활용하려는 발신자는
sync.Pool
세부 정보를 처리 할 수 있습니다. 특정 할당이 많은 메모리 압력을 발생시키는 경우, alloc이 더 이상 사용되지 않을 때를 알고 있으며 더 나은 최적화를 제공하지 않으면sync.Pool
도움이 될 수 있습니다. (CloudFlare 는 재활용에 대한 유용한 (사전sync.Pool
) 블로그 게시물을 게시했습니다 .)
- 할당을 피하기 위해 Go의 탈출 분석 은 친구입니다. 때로는 간단한 생성자, 일반 리터럴 또는 유용한 0 값으로 초기화 할 수있는 유형을 만들어 힙 할당을 피하는 데 도움이 될 수 있습니다
마지막으로 슬라이스가 포인터 여야하는지 여부에 대해 : 값 슬라이스는 유용 할 수 있으며 할당 및 캐시 누락을 저장합니다. 차단제가있을 수 있습니다.
- 아이템을 생성하는 API는 포인터를 강제 할 수 있습니다. 예를 들어
NewFoo() *Foo
Go를 0으로 초기화하지 않고 호출해야 합니다 . - 항목의 원하는 수명이 모두 같지 않을 수 있습니다. 전체 슬라이스가 한 번에 해제됩니다. 항목의 99 %가 더 이상 유용하지 않지만 다른 1 %에 대한 포인터가 있으면 모든 배열이 할당 된 상태로 유지됩니다.
- 물건을 옮기면 문제가 발생할 수 있습니다. 특히 기본 배열
append
이 커질 때 항목을 복사합니다 . 포인터append
가 잘못된 위치를 가리 키기 전에 포인터를 가져간 경우, 거대한 구조체의 경우 복사 속도가 느려질 수 있습니다sync.Mutex
. 예를 들어 복사는 허용되지 않습니다. 가운데에 삽입 / 삭제하고 비슷한 방식으로 항목을 이동합니다.
대체로 가치 조각은 모든 항목을 미리 가져 와서 움직이지 않거나 (예 : append
초기 설정 후 더 이상 s) 움직이지 않는 경우 또는 계속 움직이더라도 확실합니다. 확인 (항목에 대한 포인터 사용 /주의, 효율적으로 복사 할 수있는 작은 크기 등) 때로는 상황의 세부 사항을 생각하거나 측정해야하지만, 이는 대략적인 지침입니다.
메소드 리시버를 포인터로 사용하려는 세 가지 주요 이유 :
"먼저, 가장 중요한 방법은 수신기를 수정해야합니까? 그렇다면 수신기는 포인터 여야합니다."
"두 번째는 효율을 고려하는 것이다. 예를 들어 수신기가 큰 경우, 예를 들어 큰 구조라면 포인터 수신기를 사용하는 것이 훨씬 저렴할 것이다."
"다음은 일관성입니다. 유형의 일부 메소드에 포인터 리시버가 있어야하는 경우 나머지도 마찬가지이므로 유형 사용 방법에 관계없이 메소드 세트가 일관됩니다"
참조 : https://golang.org/doc/faq#methods_on_values_or_pointers
편집 : 또 다른 중요한 것은 함수에 전송하는 실제 "유형"을 아는 것입니다. 유형은 '값 유형'또는 '참조 유형'일 수 있습니다.
슬라이스와 맵이 참조로 작동하더라도 함수에서 슬라이스의 길이를 변경하는 것과 같은 시나리오에서 포인터와 맵을 포인터로 전달할 수 있습니다.
일반적으로 포인터를 반환해야하는 경우는 상태 저장 또는 공유 가능 리소스 의 인스턴스 를 생성 할 때 입니다. 이것은 종종 접두사가 붙은 함수에 의해 수행됩니다 .New
그것들은 무언가의 특정 인스턴스를 나타내며 어떤 활동을 조정해야 할 수도 있기 때문에 동일한 자원을 나타내는 복제 / 복사 구조를 생성하는 것은 의미가 없습니다. 따라서 반환 된 포인터는 자원 자체에 대한 핸들 역할을합니다 .
몇 가지 예 :
func NewTLSServer(handler http.Handler) *Server
-테스트를 위해 웹 서버를 인스턴스화func Open(name string) (*File, error)
-파일 접근 핸들을 반환
다른 경우에는 기본적으로 구조가 복사하기에 너무 커서 포인터가 리턴됩니다.
func NewRGBA(r Rectangle) *RGBA
-메모리에 이미지를 할당
또는 포인터를 내부에 포함하는 구조체의 복사본을 대신 반환하여 포인터를 직접 반환하지 않아도되지만 관용구로 간주되지는 않습니다.
- 표준 라이브러리에는 그러한 예제가 없습니다 ...
- 관련 질문 : 포인터 또는 값으로 Go에 임베드
참고 URL : https://stackoverflow.com/questions/23542989/pointers-vs-values-in-parameters-and-return-values
'IT' 카테고리의 다른 글
파이썬에서 IoC / DI가 일반적이지 않은 이유는 무엇입니까? (0) | 2020.03.20 |
---|---|
싱글 톤 : 어떻게 사용해야합니까 (0) | 2020.03.20 |
복제하기 전에 github 저장소의 크기를 보시겠습니까? (0) | 2020.03.20 |
설치된 .NET Framework 버전 및 서비스 팩을 어떻게 탐지합니까? (0) | 2020.03.20 |
잘못된 매직 넘버 오류는 무엇입니까? (0) | 2020.03.20 |