멤버 데이터에서 포인터 또는 참조를 선호해야합니까?


138

다음은 질문을 설명하기위한 간단한 예입니다.

class A {};

class B
{
    B(A& a) : a(a) {}
    A& a;
};

class C
{
    C() : b(a) {} 
    A a;
    B b; 
};

그래서 B는 C의 일부를 업데이트 할 책임이 있습니다. 나는 lint를 통해 코드를 실행했으며 참조 멤버 lint # 1725에 대해 엉망이었습니다 . 여기에서는 기본 복사 및 할당을 충분히 돌보는 것에 대해 이야기하지만 기본 복사 및 할당도 포인터로 나쁘므로 이점이 거의 없습니다.

나체 포인터는 그 포인터를 삭제할 책임이있는 사람을 불확실하게 소개하기 때문에 항상 참조를 사용하려고합니다. 값으로 객체를 포함하는 것을 선호하지만 포인터가 필요한 경우 포인터를 소유하는 클래스의 멤버 데이터에 auto_ptr을 사용하고 객체를 참조로 전달합니다.

포인터가 null이거나 변경 될 수있는 경우 일반적으로 멤버 데이터에 포인터 만 사용합니다. 데이터 멤버에 대한 참조보다 포인터를 선호해야하는 다른 이유가 있습니까?

참조가 초기화 된 후에는 참조를 변경해서는 안되므로 참조가 포함 된 객체를 할당 할 수 없다고 말하는 것이 사실입니까?


"하지만 기본 복사 및 할당도 포인터에 좋지 않습니다": 동일하지 않습니다. 포인터가 const가 아닌 경우 원할 때마다 변경할 수 있습니다. 참조는 일반적으로 항상 const입니다! (멤버를 "A & const a;"로 변경하려고하면이 내용을 볼 수 있습니다. 컴파일러 (적어도 GCC)는 "const"키워드가 없어도 참조가 const임을 경고합니다.
mmmmmmmm

이것의 주된 문제는 누군가가 b b (A ())를 수행하면 매달려있는 참조로 끝나기 때문에 망하게된다는 것입니다.
log0

답변:


67

참조 멤버는 클래스 구현이 수행 할 수있는 작업 (제한 연산자 할당 방지)을 제한하고 클래스가 제공 할 수있는 이점을 제공하지 않기 때문에 피하십시오.

문제 예 :

  • 각 생성자의 이니셜 라이저 목록에서 참조를 초기화해야합니다.이 초기화를 다른 함수로 인수 분해 할 수있는 방법이 없습니다 ( C ++ 0x까지 편집 : C ++에는 위임 생성자가 있습니다 )
  • 참조는 리 바인드되거나 널이 될 수 없습니다. 이것은 이점이 될 수 있지만 리 바인딩을 허용하거나 멤버가 널이되도록 코드를 변경해야하는 경우 멤버의 모든 사용을 변경해야합니다.
  • 포인터 멤버와 달리 리팩토링에 필요할 수 있으므로 참조를 스마트 포인터 또는 반복자로 쉽게 바꿀 수 없습니다.
  • 참조가 사용될 때마다 값 유형 ( .연산자 등)처럼 보이지만 포인터처럼 작동합니다 (매끄러 워질 수 있음). 예를 들어 Google 스타일 가이드

66
당신이 언급 한 모든 것은 피해야 할 좋은 것이므로 참조가 도움이된다면 좋으며 나쁘지 않습니다. 목록을 초기화하는 것이 데이터를 초기화하는 가장 좋은 장소입니다. 종종 참조가없는 할당 연산자를 숨겨야합니다. "반환 할 수 없음"-변수를 재사용하는 것은 좋지 않습니다.
Mykola Golubyev

4
@Mykola : 동의합니다. 나는 이니셜 라이저 목록에서 멤버를 초기화하는 것을 선호하고, 널 포인터를 피하고 변수의 의미를 변경하는 것은 좋지 않습니다. 우리의 의견이 다른 곳은 컴파일러가 이것을 필요로하지 않거나 필요로한다는 것입니다. 클래스를 더 쉽게 작성할 수는 없지만이 영역에서 잡을 수있는 버그를 발견하지 못했으며 포인터 멤버 (적절한 경우 스마트 포인터)를 일관되게 사용하는 코드에서 얻는 유연성에 감사합니다.
James Hopkin

클래스에 어쨌든 삭제를 담당하지 않는 포인터가 클래스에 포함되어 있으면 할당을 숨길 필요가 없으므로 할당을 숨길 필요가 없다고 생각합니다. 기본적으로 수행됩니다. 이것이 포인터가 멤버 데이터의 참조보다 선호되어야하는 이유를 제공하기 때문에 이것이 가장 좋은 대답이라고 생각합니다.
markh44

4
제임스, 코드를 작성하기 쉽게 만드는 것이 아니라 코드를 쉽게 읽을 수있게 만드는 것입니다. 데이터 멤버로 참조하면 사용 시점에서 null 일 수 있는지 궁금 할 필요가 없습니다. 이는 문맥이 덜 필요한 코드를 볼 수 있음을 의미합니다.
Len Holgate

4
사용 된 수에 따라 단위 테스트와 조롱이 훨씬 더 어려워지고 불가능 해집니다. '그래프 폭발 영향'과 함께 IMHO는 절대 사용하지 않는 충분한 이유가 있습니다.
Chris Huang-Leaver

156

내 자신의 경험 법칙 :

  • 객체의 수명이 다른 객체의 수명에 의존하기를 원할 때 참조 멤버를 사용하십시오 . 다른 클래스의 유효한 인스턴스가 없으면 객체가 살아남을 수 없다는 명시적인 방법입니다. 할당과 생성자를 통해 참조 초기화를 가져야 할 의무. 인스턴스가 다른 클래스의 멤버인지 아닌지를 가정하지 않고 클래스를 디자인하는 좋은 방법입니다. 당신은 그들의 삶이 다른 사례들과 직접 연결되어 있다고 가정합니다. 나중에 클래스 인스턴스 사용 방법을 변경할 수 있습니다 (새 인스턴스, 로컬 인스턴스, 클래스 멤버, 관리자의 메모리 풀에 의해 생성됨 등).
  • 다른 경우에 포인터 사용 : 나중에 멤버를 변경하려면 포인터 또는 const 포인터를 사용하여 지정된 인스턴스 만 읽도록하십시오. 해당 유형이 복사 가능해야한다면 어쨌든 참조를 사용할 수 없습니다.때로는 특별한 함수 호출 (예 : init ()) 후에 멤버를 초기화해야하며 포인터를 사용하는 것 외에는 선택의 여지가 없습니다. 그러나 : 모든 멤버 함수에 assert를 사용하여 잘못된 포인터 상태를 신속하게 감지하십시오!
  • 객체 수명이 외부 객체의 수명에 의존하고 해당 유형도 복사 가능 해야하는 경우 포인터 멤버를 사용하지만 생성자에서 참조 인수를 사용 하면 생성 에이 객체의 수명이 의존한다는 것을 나타내는 방식으로 표시합니다 인수의 수명 동안 구현은 포인터를 사용하여 여전히 복사 가능합니다. 이러한 멤버가 사본으로 만 변경되고 유형에 기본 생성자가 없으면 유형이 두 목표를 모두 충족해야합니다.

1
귀하와 Mykola는 정답이며 포인터가없는 (더 적은 포인터) 프로그래밍을 장려합니다.
amit

37

객체는 할당 및 비교와 같은 다른 것들을 허용하지 않아야합니다. '부서', '직원', '감독'과 같은 개체가있는 일부 비즈니스 모델을 고려할 경우 한 직원이 다른 직원에게 할당 될 경우를 상상하기 어렵습니다.

따라서 비즈니스 오브젝트의 경우 일대일 관계와 일대 다 관계를 포인터가 아닌 참조로 설명하는 것이 좋습니다.

그리고 아마도 1 또는 0의 관계를 포인터로 설명하는 것이 좋습니다.

따라서 '할당 할 수 없습니다'는 없습니다.
많은 프로그래머가 포인터와 함께 사용하기 때문에 참조 사용을 피하기 위해 인수를 찾을 수 있습니다.

회원으로서 포인터를 가지고 있으면 "만약의 경우"설명과 함께 사용하기 전에 사용자 또는 팀 구성원이 포인터를 계속해서 다시 확인해야합니다. 포인터가 0 일 수 있다면 포인터는 아마도 일종의 플래그로 사용되는데, 이는 모든 객체가 고유 한 역할을 수행해야하기 때문에 나쁘다.


2
+1 : 내가 경험 한 것과 동일합니다. 일부 일반 데이터 스토리지 클래스 만 assignemtn 및 copy-c'tor가 필요합니다. 또한보다 높은 수준의 비즈니스 개체 프레임 워크에는 "고유 필드를 복사하지 않음"및 "다른 상위 항목에 추가"등과 같은 항목을 처리하는 다른 복사 기술도 있어야합니다. 따라서 비즈니스 개체 복사에 고급 프레임 워크를 사용합니다. 낮은 수준의 과제를 허용하지 않습니다.
mmmmmmmm

2
+1 : 일부 동의 사항 : 할당 연산자가 정의되지 않도록하는 참조 멤버는 중요한 논거가 아닙니다. 다른 방식으로 올바르게 설명하면 모든 객체에 값 의미가있는 것은 아닙니다. 또한 논리적 인 이유없이 코드 전체에 'if (p)'가 흩어져 싶지 않다는 데 동의합니다. 그러나 그것에 대한 올바른 접근 방식은 클래스 불변을 통한 것입니다. 잘 정의 된 클래스는 멤버가 null 일 수 있는지 여부에 대해 의심의 여지가 없습니다. 코드에서 포인터가 null 일 수 있다면 주석이 잘 달릴 것으로 기대합니다.
James Hopkin

@JamesHopkin 저는 잘 설계된 클래스가 효과적으로 포인터를 사용할 수 있다는 데 동의합니다. NULL에 대한 보호 외에도 참조는 참조가 초기화되었음을 보장합니다 (포인터를 초기화 할 필요가 없으므로 다른 것을 가리킬 수 있음). 참조 는 또한 소유권, 즉 개체가 구성원을 소유하지 않는다는 것을 알려줍니다. 집계 멤버에 지속적으로 참조를 사용하면 포인터 멤버가 복합 객체라는 확신이 매우 높아집니다 (복합 멤버가 포인터로 표시되는 경우는 많지 않음).
weberc2


6

몇 가지 중요한 경우에는 할당 가능성이 필요하지 않습니다. 이들은 종종 범위를 벗어나지 않고 계산을 용이하게하는 경량 알고리즘 래퍼입니다. 이러한 객체는 항상 유효한 참조를 보유하고 복사 할 필요가 없기 때문에 참조 멤버의 주요 후보입니다 .

이러한 경우 할당 연산자 (및 종종 복사 생성자)를 사용할 수 없도록 ( boost::noncopyable 없도록 (비공개 하거나 선언하여) .

그러나 사용자 pts가 이미 언급했듯이 대부분의 다른 객체에서도 마찬가지입니다. 여기서 참조 멤버를 사용하는 것은 큰 문제가 될 수 있으며 일반적으로 피해야합니다.


6

모든 사람이 일반적인 규칙을 제시하는 것처럼 두 가지를 제안합니다.

  • 절대로 참조를 클래스 멤버로 사용하지 마십시오. 나는 내 자신의 코드에서 그렇게하지 않았으며 (이 규칙에서 옳았다는 것을 스스로에게 증명하지는 않는다) 내가 그렇게 할 경우를 상상할 수 없다. 의미론이 너무 혼란스럽고 실제로 참조가 설계된 것이 아닙니다.

  • 기본 유형을 제외하고 함수에 매개 변수를 전달할 때 또는 알고리즘에 사본이 필요한 경우 항상 참조를 사용하십시오.

이 규칙은 간단하며 좋은 입장에 서있었습니다. 나는 똑똑한 포인터 (그러나 auto_ptr이 아닌 제발)를 다른 사람들의 클래스 멤버로 사용하는 규칙을 남깁니다.


2
첫 번째 의견에는 동의하지 않지만, 의견 불일치는 이론적 가치를 평가하는 문제가 아닙니다. +1
David Rodríguez-dribeas

(우리는 SO의 좋은 유권자들과 함께이 논쟁을 잃고있는 것 같습니다)
James Hopkin

2
"반드시 참조를 반원으로 사용하지 마십시오"라는 이유로 투표권을 행사했으며 다음과 같은 정당성이 나에게는 맞지 않습니다. 내 답변 (및 최고 답변에 대한 의견)과 내 경험에서 설명했듯이 단순히 좋은 일이 있습니다. 나는 그들이 그것을 좋은 방법으로 사용하고있는 회사에 고용 될 때까지 당신과 같은 생각을하고 그것이 도움이되는 경우가 있다는 것을 나에게 분명히했다. 실제로 실제 문제는 프로그래머가 프로그래머가 수행하는 작업을 이해하도록 요구하기 때문에 참조를 사용하게하는 구문 및 숨겨진 의미에 관한 것입니다.
Klaim

1
Neil> 동의합니다. 그러나 "투표"한 것은 "투표"가 아니며, "절대"주장입니다. 여기서 논쟁하는 것은 어쨌든 도움이되지 않는 것 같습니다. 내 다운 투표를 취소합니다.
Klaim

2
이 답변은 참조 멤버 의미가 혼동되는 부분을 설명함으로써 향상 될 수 있습니다. 이 이론적 근거는 포인터가 의미 상 모호하게 보이기 때문에 (초기화되지 않을 수 있으며, 기본 객체의 소유권에 대해서는 아무 것도 알려주지 않기 때문에) 중요합니다.
weberc2 2016

4

예 : 참조가 초기화 된 후에는 참조를 변경할 수 없으므로 참조를 포함하는 객체를 할당 할 수 없다고 말하는 것이 사실입니까?

데이터 멤버를위한 나의 경험 법칙 :

  • 할당을 막기 때문에 참조를 사용하지 마십시오
  • 클래스가 삭제를 담당하는 경우 boost의 scoped_ptr (auto_ptr보다 안전한)을 사용하십시오.
  • 그렇지 않으면 포인터 또는 const 포인터를 사용하십시오.

2
비즈니스 객체를 할당해야 할 경우에도 이름을 지정할 수 없습니다.
Mykola Golubyev

1
+1 : auto_ptr 멤버에주의를 기울일 것입니다. 멤버가있는 모든 클래스에는 명시 적으로 정의 된 할당 및 복사 구조가 있어야합니다 (생성 된 클래스는 필요한 것 같지 않음)
James Hopkin

auto_ptr도 (지각 적) 할당을 방지합니다.

2
@Mykola : 또한 비즈니스 객체의 98 %가 할당 또는 복사 작성자가 필요하지 않은 경험을했습니다. 나는 복사 c'tors와 operator = ()를 구현없이 비공개로 만드는 클래스의 헤더에 추가하는 작은 매크로로 이것을 방지합니다. 따라서 객체의 98 %에서 나는 참조를 사용하며 안전합니다.
mmmmmmmm

1
멤버로서의 참조는 클래스의 인스턴스 수명이 다른 클래스의 인스턴스 수명에 직접적으로 의존한다고 명시 적으로 표현하는 데 사용될 수 있습니다. "할당을 막기 때문에 참조를 사용하지 마십시오"는 모든 클래스가 할당을 허용해야한다고 가정합니다. 모든 클래스가 복사 작업을 허용해야한다고 가정하는 것과 같습니다.
Klaim

2

포인터가 null이거나 변경 될 수있는 경우 일반적으로 멤버 데이터에 포인터 만 사용합니다. 데이터 멤버에 대한 참조보다 포인터를 선호해야하는 다른 이유가 있습니까?

예. 코드의 가독성. 포인터를 사용하면 멤버가 참조 (아이 론적으로 :))이며 포함 된 객체가 아니라는 것을 더 분명하게 알 수 있습니다. 어떤 사람들은 그것이 구식이라고 생각하지만 여전히 혼란과 실수를 막는다 고 생각합니다.


1
Lakos는 또한 "대규모 C ++ 소프트웨어 디자인"에서 이에 대한 주장을합니다.
Johan Kotlinski 10

Code Complete도 이것을 옹호한다고 생각하며 반대하지 않습니다. 그것은 너무 설득력이 없습니다 ...
markh44

2

누가 수업에서 파생 될 것인지, 무엇을하고 싶을 지 모르기 때문에 참조 데이터 멤버에 대해 조언하지 않습니다. 그들은 참조 된 객체를 사용하고 싶지 않을 수도 있지만 참조로서 유효한 객체를 제공하도록 강요했습니다. 참조 데이터 멤버 사용을 중지하기에 충분할 정도로이 작업을 수행했습니다.


0

질문을 올바르게 이해하면 ...

포인터 대신 함수 매개 변수로서의 참조 : 지적했듯이 포인터는 포인터의 정리 / 초기화를 누가 소유하고 있는지 명확하게하지 않습니다. 포인터를 원할 때 공유 지점을 선호하십시오. 포인터는 C ++ 11의 일부이며, 지정된 데이터를 허용하는 클래스의 수명 동안 데이터의 유효성이 보장되지 않는 경우 weak_ptr입니다.; 참조를 함수 매개 변수로 사용하면 참조가 null이 아닌 것을 보장 할 수 있습니다. 이 문제를 해결하려면 언어 기능을 바꾸어야하며 느슨한 대포 코더는 신경 쓰지 않습니다.

참조 aa 멤버 변수 : 데이터 유효성과 관련하여 위와 같습니다 . 이 데이터에 대해 지적하고 참조한 보증이 유효합니다.

변수 유효성에 대한 책임을 코드의 이전 지점으로 옮기면 이후 코드 (예에서 클래스 A)가 정리 될뿐만 아니라 사용하는 사람에게 명확하게됩니다.

약간 혼란스러운 예에서 (실제로 더 명확한 구현을 찾으려고 노력하고 있습니다) B가 사용하는 A는 B의 수명 동안 보장됩니다 .B는 A의 멤버이므로 참조는 이것을 강화합니다. (아마도) 더 명확합니다.

그리고 (코드 컨텍스트에서 의미가 없기 때문에 가능성이 낮은 후드 인 경우), 참조되지 않은 비 포인터 A 매개 변수를 사용하고 A를 복사하고 패러다임을 쓸모 없게 만드는 또 다른 대안 실제로 이것을 대안으로 생각하지는 않습니다.

또한 포인터를 수정할 수있는 참조 된 데이터를 변경할 수 없습니다. const 포인터는 데이터에 대한 참조 / 포인터가 변경 불가능한 경우에만 작동합니다.

포인터는 B의 A 매개 변수가 설정되지 않았거나 재 할당 될 수있는 경우에 유용합니다. 때로는 약한 포인터가 구현하기에 너무 장황하고 많은 사람들이 weak_ptr이 무엇인지 알지 못하거나 싫어합니다.

이 답변을 찢어주세요. :)

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