부모 포인터에 대한 순환 참조는 언제 허용됩니까?


24

이 스택 오버플로 질문은 포인터를 통해 부모를 참조하는 자식에 관한 것입니다.

처음에는 디자인이 끔찍한 아이디어라는 의견이 매우 중요했습니다.

나는 이것이 일반적으로 최고의 아이디어는 아니라는 것을 이해합니다. 일반적으로 "이러지 마!"라고 말하는 것이 공정한 것 같습니다.

그러나, 나는 당신이 이와 같은 일을해야 할 곳에 어떤 종류의 조건이 존재할지 궁금합니다. 여기 의이 질문 과 관련 답변 / 주석은 그래프가 이와 같은 것을 수행하지 않도록 제안합니다.


1
연결 한 질문은 주제에 대해 매우 포괄적 인 것 같습니다.
Monica와의 가벼움 경주

4
@LightnessRacesinOrbit "하지 마십시오"는 왜 그런지 이해하는 한 실제로 유용하지 않습니다 .
enderland

2
나는 거기에 "하지 마라"보다 더 많은 것을 본다. 여러 전문가가 논의한 장단점이 있습니다.
Monica와의 가벼움 경주

1
순회가 필요한 양방향 목록, 일종의 순환 버퍼가있을 수 있습니다. 아마도 게임에서 연결된 두 조각의 도로를 나타내는 것일 수 있습니다. 원형을 표현 해야하는 경우 좋은 아이디어 일 수 있습니다.
rhughes

2
엄지 손가락에 대한 나의 실용적인 규칙은 "자녀가 부모없이 존재할 수 있습니까?"라는 질문입니다. (XmlDocuments 및 해당 노드를 고려하는 경우 : 문서의 트리 컨텍스트가 없으면 노드가 존재할 수 없습니다. 말이되지 않습니다.) 대답이 없으면 양방향 링크가 모두 가능합니다. 두 개체 만있을 수 있습니다. 객체 가 독립적으로 존재할 있으면 두 링크 중 하나를 제거합니다.
mikalai

답변:


43

여기서 핵심은 두 객체에 순환 참조가 있는지 여부가 아니라 해당 참조 가 서로의 소유권 을 나타내는 지 여부 입니다.

두 개체는 서로 "소유"할 수 없습니다. 이로 인해 초기화 및 삭제 순서에 난처한 딜레마가 발생합니다. 하나는 선택적 참조 여야하며, 그렇지 않으면 한 개체가 다른 개체의 수명을 관리하지 않음을 나타냅니다.

이중 연결 목록을 고려하십시오. 두 노드는 서로 앞뒤로 연결되지만 다른 노드를 "소유"하지 않습니다 (목록은 두 노드를 모두 소유 함). 이것은 어느 노드도 다른쪽에 메모리를 할당하거나 다른 쪽의 아이덴티티 또는 수명 관리를 책임지지 않습니다.

나무의 노드는 자식을 할당 할 수 있고 부모는 자식을 소유하지만 트리는 비슷한 관계를 갖습니다. 자녀에서 부모로의 링크는 순회를 돕지 만 소유권을 다시 정의하지는 않습니다.

대부분의 OO 디자인에서 객체의 데이터 멤버 인 다른 객체에 대한 참조는 소유권을 의미합니다. 예를 들어 Car 및 Engine 클래스가 있다고 가정합니다. 둘 다 그 자체로는 매우 유용하지 않습니다. 우리는 이러한 객체 들이 서로 의존 한다고 말할 수 있습니다 . 유용한 작업을 수행하려면 서로의 존재가 필요합니다. 그러나 어느 쪽이 다른 쪽을 "소유"합니까? 이 경우 자동차는 모든 자동차 구성 요소가있는 "컨테이너"이기 때문에 자동차가 엔진을 소유하고 있다고 말할 수 있습니다. OO와 실제 디자인 모두에서 자동차는 부품의 합계이며 모든 부품은 자동차의 맥락에서 함께 연결됩니다. 엔진에 자동차에 대한 참조가 있거나 TorqueConverter에 대한 참조가있을 수 있습니다.

순환 참조 나쁜 디자인 냄새 일 수 있지만 반드시 그런 것은 아닙니다. 신중하게 사용하고 올바르게 문서화하면 데이터 구조를보다 쉽게 ​​사용할 수 있습니다.

부모와 자녀 사이에 길을 가지지 않고 나무를 가로 지르십시오. 물론, 취성적이고 복잡한 스택 기반 접근 방식을 생각해 보거나 아주 간단한 참조 기반 접근 방식을 사용할 수 있습니다.


16

이러한 설계에서 고려해야 할 몇 가지 측면이 있습니다.

  • 구조적 의존성
  • 소유권 관계 (예 : 컴포지션과 다른 종류의 연관)
  • 탐색 필요

클래스 간의 구조적 종속성 :

컴포넌트 클래스를 재사용하려는 경우 불필요한 종속성을 피하고 닫힌 순환 구조를 피해야합니다.

그럼에도 불구하고 때로는 두 개의 클래스가 개념적으로 강력하게 연결되어 있습니다. 이 경우 종속성을 피하는 것이 실제 옵션이 아닙니다. 예 : 나무와 그 잎, 또는보다 일반적 으로 복합물과 그 구성 요소 .

객체의 소유권 :

한 개체가 다른 개체를 소유합니까? 또는 달리 언급하십시오 : 한 물체가 파괴되면 다른 물체도 파괴됩니까?

이 주제는 Snowman이 자세히 다루었으므로 여기서는 다루지 않겠습니다.

객체 간 탐색 필요 :

마지막 문제는 탐색이 필요합니다. 내가 가장 좋아하는 예인 Gang of four합성 디자인 패턴 을 보자 .

감마 & 알. " 자식 컴포넌트에서 부모로의 참조를 유지하면 복합 구조의 순회 및 관리가 단순화 될 수 있습니다. "물론 체계적인 하향식 순회를 상상할 수 있지만 매우 큰 복합 객체의 경우 작업을 기하 급수적으로 늦출 수 있습니다. 직접 참조, 심지어 원형은 복합재의 조작을 상당히 쉽게 할 수 있습니다.

전자 시스템의 그래픽 모델을 예로들 수 있습니다. 복합 구조는 전자 보드, 회로, 요소를 나타낼 수 있습니다. 모델을 표시하고 조작하려면 GUI보기에 기하학적 프록시가 필요합니다. 그런 다음 하향식 검색을 시작하는 것보다 사용자가 선택한 GUI 요소에서 구성 요소로 이동하여 부모 요소와 관련 형제 / 자매 요소를 찾는 것이 훨씬 쉽습니다.

물론 Gamma & al이 지적했듯이 순환 관계의 불변성을 보장해야합니다. 언급 한 SO 질문에 표시된 것처럼 까다로울 수 있습니다. 그러나 완벽하게 관리 가능하고 안전한 방식입니다.

결론

내비게이션 필요성을 과소 평가해서는 안된다. UML이 모델링 표기법으로 UML을 명시 적으로 다룬 이유는 없습니다. 그리고 그렇습니다. 순환 참조가 필요한 완벽한 상황이 있습니다.

유일한 요점은 사람들이 때때로 그러한 방향으로 빨리가는 경향이 있다는 것입니다. 따라서 결정을 내릴 것인지 결정하기 전에 관련된 세 가지 측면을 모두 고려해야합니다.


1
이 답변은 IMHO가 크게 찬성했습니다. OP는 다음과 같이 물었다. " 이미 부모-자녀 관계 주어 졌을 때 , 순환 참조로이를 구현해도 괜찮은가?" 따라서 구조와 "소유권"(여기에서 언급 한 의미)은 이미 명확합니다. 즉, 한쪽 또는 다른쪽에 참조를 추가 하는 유일한 기준은 "탐색 요구"와 "자녀의 독립적 재사용"이라는 질문입니다.
Doc Brown

@DocBrown-이 질문에 적격이되면 언제든지 현상금을 지급 할 수 있습니다. :-)

1
@ GlenH7 :이 답변에 더 많은 투표권을주지 않고 Snowman의 답변 만 제공합니다 (질문의 요점을 다소 놓치고 있다고 생각합니다).
Doc Brown

1
@DocBrown-그러나 repz, 남자, repz!

... 공공 개인 보호와 같은 가시성 선택을 추가하면 9 가지 옵션이 제공됩니다.
mikalai

8

일반적으로 순환 참조는 순환 종속성을 의미하기 때문에 매우 나쁜 아이디어입니다. 순환 지연이 나쁜 이유를 이미 알고 있지만 완전성을 위해 tl; dr 버전은 클래스 A와 B가 서로 의존 할 때마다 A 또는 B를 이해하지 않고 수정하거나 수정할 수 없다는 것입니다. 다른 클래스를 동시에 이해 / 고정 / 최적화 등 모든 것을 바꾸지 않고는 아무것도 바꿀 수없는 코드베이스로 빠르게 이어집니다.

그러나 악 순환 종속성을 만들지 않고 원형을 참조 할 수있다. 이것은 기능적인 관점에서 참조가 엄격하게 선택적인 한 작동합니다. 즉, 수업에서 쉽게 제거 할 수 있으며 느리게 작동하더라도 여전히 작동합니다. 이러한 순환 비 종속 작성 참조에 대해 알고있는 주요 유스 케이스는 링크 된 목록, 트리 및 힙과 같은 노드 기반 데이터 구조의 빠른 순회를 가능하게하는 것입니다. 예를 들어, 이중 연결 목록에서 수행 할 수있는 모든 작업 (단일 연결 목록에서도 수행 할 수있는 작업)은 훨씬 더 큰 몇 가지 작업 (목록을 통해 뒤로 이동)이 발생합니다. 이중 연결 버전의 경우 O


6

일반적으로 이렇게하는 것이 좋지 않은 이유는 종속성 반전 원리를 위반하기 때문 입니다. 사람들은이 게시물에서 적절하게 다룰 수있는 것보다 훨씬 자세하게 이것에 대해 많이 작성했지만 커플 링이 너무 빡빡하기 때문에 유지하기가 어려워집니다. 두 클래스를 변경하면 거의 항상 다른 클래스의 변경이 필요하지만, 종속성이 한 가지 방식 만 가리키는 경우 인터페이스의 한쪽에 대한 변경이 격리됩니다. 두 클래스가 모두 추상 인터페이스를 가리키면 더 좋습니다.

하나의 주요 예외는 다른 추상화 레벨에 두 개의 서로 다른 클래스가 없지만 트리, 이중 연결 목록 등과 같은 동일한 클래스의 두 개의 노드가있는 경우입니다. 추상화 관계. 알고리즘 효율성을 위해 순환 참조가 허용되며 이러한 종류의 경우에도 권장됩니다.


5

[...]는 그래프가 이런 식으로하지 않도록 제안합니다.

때로는 트리와 다른 데이터 구조에서 상향식으로 사물에 액세스해야하는 반면 트리는 하향식으로 사물에 액세스해야합니다.

예를 들어, 쿼드 트리는 벡터 그래픽 소프트웨어에 요소를 저장할 수 있습니다. 그러나 사용자의 선택은 벡터 요소 참조 / 포인터의 개별 선택 목록에 저장됩니다. 사용자가 해당 선택을 삭제하려면 쿼드 트리를 업데이트해야하며 하향식이 아닌 잎에서 시작하여 상향식으로 트리를 업데이트하는 것이 훨씬 더 효율적일 수 있습니다. 그렇지 않으면 각 요소에 대해 루트에서 리프로 작업 한 다음 다시 백업해야합니다.


1
예,이 예는 실제로 적절합니다. 복합물의 내비게이션에 대한 나의 주장에서 내가 설명하려고했던 것의 정확한 종류 : 백 포인터가 상당히 빠른 속도를 낸다. 흥미로운 공연 일화와 매우 명확한 다이어그램에 감사드립니다! +1
Christophe

@Christophe 당신의 모범도 좋아합니다! 실제로 질문에 올바르게 대답했는지 확실하지 않았습니다. 아마도 데이터 구조를 위 / 뒤로 이동할 수있는 백 포인터보다 "순환 소유권"에 관한 것이기 때문입니다. 그러나 나는 질문의 마지막 "그래프"부분에 주로 응답했다.

2

Doom 3에는 부모 개체에 대한 포인터가있는 자식 개체의 예가 있습니다. 특히 그것은 침입 목록을 사용 합니다 . 요약하면, 침입 목록은 각 노드가 목록 자체에 대한 포인터를 포함한다는 점을 제외하면 연결된 목록과 같습니다.

장점 :

  • 객체가 여러 목록에 동시에 존재할 수있는 경우 목록 노드의 메모리를 한 번만 할당하고 할당 해제해야합니다.

  • 객체를 폐기해야하는 경우 각 목록을 선형으로 검색하지 않고도 객체가있는 모든 목록에서 쉽게 제거 할 수 있습니다.

나는 이것이 매우 구체적인 시나리오라고 생각하지만 귀하의 질문을 이해하면 부모 개체에 대한 포인터가 포함 된 자식 개체를 사용할 수있는 예입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.