참조하는 대상에 새로운 인스턴스가 할당되었을 때

객체지향 프로그래밍을 하다보면 개체를 참조하는 것으로 개체와 개체간 다양한 관계를 맺어 문제를 풀게 되는데요,

참조하고 있는 개체가 새로운 인스턴스가 되었을 경우 참조하고 있는 쪽에서는 알아서 감지를 할 수는 없습니다. 이런 모양새가 되죠.

이것을 개선할 방법을 코드로 살펴보았습니다.

Console.WriteLine("orignal = new IntValue(15)");
var orignal = new IntValue(15);
var refInstance = orignal;
var refInstance2 = (Ref<IntValue>)(() => orignal);
ref var refInstance3 = ref orignal;
Console.WriteLine($"orign\t: {orignal}");
Console.WriteLine($"=\t: {refInstance}");
Console.WriteLine($"Ref<T>\t: {refInstance2.Get()}");
Console.WriteLine($"ref\t: {refInstance3}");

Console.WriteLine();

orignal = new IntValue(2);
Console.WriteLine("orignal = new IntValue(2)");

Console.WriteLine($"orign\t: {orignal}");
Console.WriteLine($"=\t: {refInstance}");
Console.WriteLine($"Ref<T>\t: {refInstance2.Get()}");
Console.WriteLine($"ref\t: {refInstance3}");

record IntValue(int Value);

readonly record struct Ref<T>(Func<T> Get)
{
    //public static explicit operator Ref<T>(Func<T> Get) => new Ref<T>(Get);
    public static implicit operator Ref<T>(Func<T> Get) => new(Get);
}

| 출력

orignal = new IntValue(15)
orign   : IntValue { Value = 15 }
=       : IntValue { Value = 15 }
Ref<T>  : IntValue { Value = 15 }
ref     : IntValue { Value = 15 }

orignal = new IntValue(2)
orign   : IntValue { Value = 2 }
=       : IntValue { Value = 15 }
Ref<T>  : IntValue { Value = 2 }
ref     : IntValue { Value = 2 }

이런 경우 여러분은 어떻게 해결하시나요?

5 Likes

객체라면 복사 생성자, 할당 연산자 오버로딩
스마트 포인터 클래스에서 필수적인 구현이죠

1 Like

저한테는 난해한 주제네요.

참조 객체가 바뀐 것을 알 필요가 있는 케이스가 언제이며,
보여주신 코드가 어떻게 알려준다는 것인지…

이해하기 힘듭니다. ㅠㅠ

요 두개만 있어도 original에 새로운 인스턴스가 있다는 것을 알 수 있는 것 아닌가요?

1 Like

아 참조하는 인스턴스가 변경된 케이스 입니다.

위의 그림과 같이 IntValue(15)에서 새로운 인스턴스인 IntValue(2)로 바뀌었음에도 refInstance는 IntValue(15)를 가리키죠. 알아서 변경된 인스턴스 IntValue(2)를 가리키면 좋을텐데 말이죠.

이런 케이스가 다른 분들도 있으셨을까 궁금해서 올렸습니다.

2 Likes

그런 의미라면 아래의 두 개 만으로도 알 수 있는 것 아닌가요?

이 주제에 합당한 얘기지는 잘 모르겠지만, 저는 사실 자주 호출되는 메서드라면 반환보다는 in, ref, out 을 애용하는 편입니다.

void GetValue(in MyData data, out ValueType value)
{
} 

변수에 대한 할당/복사도 막고, return 도 복사이기 때문에, 성능이 최소 두 배 이상 좋아지거든요.
특히 필터링할 데이터가 많을 경우, 반드시 씁니다. 10배 정도 속도 차이가 나는 것도 경험을 했거든요.

3 Likes

Rust로 한 번 해봤어요

    let mut original = 15;
    let ref_inst = &original.clone();
    println!("original = {}", original);
    println!("refInst = {}\n", ref_inst);

    original = 2;
    println!("original = {}", original);
    println!("refInst = {}", ref_inst);

출력:
original = 15
refInst = 15

original = 2
refInst = 15

let ref_inst = &original.clone(); 클론으로 복제하지 않으면 오류납니다.
cannot assign to ‘original’ because it is borrowed

2 Likes

그냥 스치듯 든 생각은… setter 내부 구현입니다… :blush:

2 Likes

번뜩 떠오른 생각으로는,
Notify를 하게 되는 가장 흔한 케이스가 이런 이유 아닐까 싶네요 ㅎㅎ;

1 Like