Go에서 예약어 defer는 주변 함수가 종료될 때까지 함수 실행을 연기합니다. 이는 자원을 해제하고 마무리 작업을 하는 데 유용합니다. 그러나 포인터(*T)나 값(T)을 받는 메서드와 defer를 사용할 때는 명백하지 않은 뉘앙스가 있습니다.
코드의 안전성 측면에서 Go는 함수의 어떤 종료 지점에서도 청소 코드를 자동으로 호출하도록 설계되었습니다. 그러나 사용되는 리시버의 타입(포인터 또는 값)은 defer를 호출하는 메서드의 객체가 복사되는지, 원래 객체가 변경되는지를 결정합니다.
리시버가 값(T)인 메서드를 호출하면, defer에서 구조체의 값이 복사됩니다. 리시버가 포인터(*T)인 메서드를 호출하면 메서드는 원래 객체로 작업합니다. 그 결과, 값에 의해 defer 메서드에서 데이터가 변경되면 이는 감지되지 않지만 포인터에 의해 변경된 데이터는 외부 구조체에 반영됩니다. 이는 특히 defer를 통해 객체의 상태를 변경하려고 시도할 때 성가신 오류로 이어질 수 있습니다.
메서드를 설계하고 defer를 사용할 때는 항상 리시버의 타입을 의식적으로 선택해야 합니다. 함수 끝까지 지속되어야 하는 변경은 포인터를 통해 수행해야 합니다.
코드 예시:
package main import "fmt" type Counter struct { Value int } func (c Counter) IncValue() { // 값의 메서드 defer func() { c.Value++ // 복사본만 증가합니다. fmt.Println("[Value receiver defer] Value:", c.Value) }() } func (c *Counter) IncPointer() { // 포인터의 메서드 defer func() { c.Value++ // 원본이 증가합니다. fmt.Println("[Pointer receiver defer] Value:", c.Value) }() } func main() { c := Counter{Value: 10} c.IncValue() // Value는 여전히 10입니다. c.IncPointer() // Value는 11이 됩니다. fmt.Println("Original Value after calls:", c.Value) }
주요 특징:
1. defer에서 값 리시버 메서드를 호출하면 외부 객체가 변경될까요?
아니요, defer에서는 원래 객체가 변경되지 않습니다. 메서드는 구조체의 복사본과 작업하기 때문입니다.
2. 값 메서드를 통해 구조체의 상태를 보장하기 위해 defer를 신뢰할 수 있나요?
아니요. 원본 객체의 변경을 반영하려면 포인터 메서드를 사용해야 합니다.
3. defer 선언 후 구조체의 필드를 변경하면 어떻게 되나요?
리시버 전달 방법에 따라 다릅니다. 메서드/함수가 복사본을 받으면, defer 선언 후의 변경 사항은 지연된 함수에서 감지되지 않습니다.
func (c Counter) Demo() { defer fmt.Println("defer c.Value:", c.Value) // 현재 값을 캡처합니다. c.Value = 42 // defer에는 영향을 미치지 않습니다. }
연결 종료 함수가 값 메서드를 통해 상태를 업데이트하는 데 사용되었습니다. 종료 플래그가 업데이트되지 않는 것으로 나타났습니다.
장점:
단점:
원본 객체를 변경하는 포인터 리시버 메서드로 마무리 작업을 수행했습니다.
장점:
단점: