C ++에서 포인터를 지나서 참조로 전달하면 이점이 있습니까?


225

C ++에서 참조로 전달하는 것보다 포인터로 전달하는 것의 이점은 무엇입니까?

최근에 참조로 전달하는 대신 포인터로 함수 인수를 전달하도록 선택한 많은 예제를 보았습니다. 이렇게하면 이점이 있습니까?

예:

func(SPRITE *x);

의 전화로

func(&mySprite);

vs.

func(SPRITE &x);

의 전화로

func(mySprite);

new포인터와 결과 소유권 문제를 만드는 것을 잊지 마십시오 .
Martin York

답변:


216

포인터는 NULL 매개 변수를 수신 할 수 있지만 참조 매개 변수는 수신 할 수 없습니다. "객체 없음"을 전달할 가능성이있는 경우 참조 대신 포인터를 사용하십시오.

또한 포인터로 전달하면 객체가 값 또는 참조로 전달되는지 여부를 호출 사이트에서 명시 적으로 볼 수 있습니다.

// Is mySprite passed by value or by reference?  You can't tell 
// without looking at the definition of func()
func(mySprite);

// func2 passes "by pointer" - no need to look up function definition
func2(&mySprite);

18
불완전한 답변. 포인터를 사용한다고해서 임시 / 승격 된 객체의 사용이나 뾰족한 객체의 사용을 스택과 같은 객체로 사용할 수있는 것은 아닙니다. 그리고 대부분 NULL 값을 금지해야 할 때 인수가 NULL 일 수 있음을 제안합니다. 완전한 답변을 얻으려면 litb의 답변을 읽으십시오.
paercebal

두 번째 함수 호출에는 주석이 달렸습니다 func2 passes by reference. 코드 수준의 관점에서 포인터를 전달하여 구현 한 높은 수준의 관점에서 "참조로"전달한다는 의미는 있지만 매우 혼란 스러웠습니다 ( stackoverflow.com/questions/13382356/… 참조 ).
궤도에서 가벼움 경주

나는 이것을 사지 않는다. 그렇습니다. 포인터를 넘겨 주므로 출력 매개 변수 여야합니다. 왜냐하면 무엇을 지적 할 수 없기 때문입니까?
deworde December

C에서이 전달 참조가 없습니까? 코드 블록 (mingw) 최신 버전을 사용하고 있으며 C 프로젝트를 선택하고 있습니다. 여전히 참조로 전달 (func (int & a)) 작동합니다. 아니면 C99 또는 C11 이상에서 사용할 수 있습니까?
Jon Wheelock

1
@ JonWheelock : 아니요, C에는 참조 기준이 전혀 없습니다. func(int& a)모든 버전의 표준에서 유효한 C가 아닙니다. 우연히 파일을 C ++로 컴파일하고있을 것입니다.
Adam Rosenfield

245

포인터로 전달

  • 발신자가 주소를 가져와야합니다-> 투명하지 않음
  • 의미하는 0 값을 제공 할 수 있습니다 nothing. 선택적 인수를 제공하는 데 사용할 수 있습니다.

참조로 전달

  • 발신자는 객체-> 투명을 전달합니다. 포인터 유형에 대한 과부하는 불가능하므로 (포인터는 내장 유형) 연산자 과부하에 사용해야합니다. 따라서 string s = &str1 + &str2;포인터 를 사용할 수 없습니다 .
  • 0 값이 가능하지 않음-> 호출 된 함수는 값을 확인할 필요가 없습니다.
  • const에 대한 참조는 temporaries :도 허용합니다. 임시 void f(const T& t); ... f(T(a, b, c));주소를 사용할 수 없으므로 포인터를 사용할 수 없습니다.
  • 마지막으로, 참조는 사용하기 쉬우 며 버그 가능성이 적습니다.

7
포인터로 전달하면 '소유권이 이전 되었습니까?' 질문. 이것은 참고 문헌이 아닙니다.
Frerich Raabe 님이

43
"버그 가능성이 적습니다"라는 의견에 동의하지 않습니다. 호출 사이트를 검사 할 때 독자에게 "foo (& s)"가 표시되면 s가 수정 될 수 있음을 즉시 알 수 있습니다. "foo (s)"를 읽을 때 s가 수정 될 수 있는지 전혀 명확하지 않습니다. 이것은 버그의 주요 원인입니다. 아마도 특정 클래스의 버그가 발생할 가능성은 적지 만 전반적으로 참조를 통해 전달하는 것이 큰 버그의 원인입니다.
윌리엄 Pursell

25
"투명한"은 무슨 뜻입니까?
Gbert90

2
@ Gbert90, 호출 사이트에 foo (& a)가 표시되면 foo ()가 포인터 유형을 사용한다는 것을 알고 있습니다. foo (a)가 보이면 참조가 필요한지 알 수 없습니다.
Michael J. Davenport

3
@ MichaelJ.Davenport-설명에서 "투명한"은 "호출자가 포인터를 전달하고 있지만 호출자가 참조를 전달한다는 것은 분명하지 않습니다"라는 행을 따라 무언가를 의미하는 것이 좋습니다. Johannes의 게시물에서 "포인터로 전달-발신자가 주소를 가져와야 함-투명하지 않음"및 "통화로 참조-발신자가 객체를 전달 함-> 투명 함"이라고 말합니다. . Gbert90의 ""투명한 "의 의미는 여전히 유효합니다.
해피 그린 키드 낮잠

66

"cplusplus.com :"의 기사에 의한 추론이 마음에 듭니다.

  1. 함수가 매개 변수를 수정하지 않고 값을 복사하기 쉬운 경우 (int, double, char, bool 등 ... 단순 유형 std :: string, std :: vector 및 기타 모든 STL 컨테이너는 단순한 유형이 아닙니다.)

  2. 값을 복사하는 데 비용이 많이 들고 함수가 AND를 가리키는 값을 수정하지 않으려는 경우 const 포인터로 전달합니다. NULL은 함수가 처리하는 유효한 예상 값입니다.

  3. 값이 복사하기에 비싸고 함수가 AND를 가리키는 값을 수정하려고 할 때 상수가 아닌 포인터로 전달합니다. NULL은 함수가 처리하는 유효한 예상 값입니다.

  4. 값이 복사하기에 고가이고 함수가 AND를 참조하는 값을 수정하지 않으려는 경우 const 참조로 전달 포인터가 대신 사용 된 경우 NULL은 유효한 값이 아닙니다.

  5. 값이 복사하기에 비싸고 함수가 AND를 참조하는 값을 수정하려고 할 때 연속이 아닌 참조로 전달 포인터가 대신 사용 된 경우 NULL은 유효한 값이 아닙니다.

  6. 템플릿 함수를 작성할 때이 논의의 범위를 벗어난 것으로 고려해야 할 몇 가지 상충 관계가 있기 때문에 명확한 대답이 없지만 대부분의 템플릿 함수는 값 또는 (const) 참조로 매개 변수를 사용한다고 말할 수 있습니다. 그러나 반복자 구문은 포인터 ( "역 참조"에 대한 별표)와 유사하기 때문에 반복자를 인수로 사용하는 템플릿 함수는 기본적으로 포인터도 허용합니다 (NULL 반복자 개념에 다른 구문이 있으므로 NULL을 확인하지 않음) ).

http://www.cplusplus.com/articles/z6vU7k9E/

내가 이것에서 취하는 것은 포인터 또는 참조 매개 변수를 사용하도록 선택하는 것의 주요 차이점은 NULL이 허용 가능한 값인지입니다. 그게 다야.

값이 입력, 출력, 수정 가능 여부 등은 결국 기능에 대한 문서 / 의견에 있어야합니다.


예, 나를 위해 NULL 관련 용어가 주요 관심사입니다. 인용을위한 Thx ..
binaryguy

62

Allen Holub의 "발로 몸을 쏠 수있는 충분한 밧줄"에는 다음 두 가지 규칙이 있습니다.

120. Reference arguments should always be `const`
121. Never use references as outputs, use pointers

그는 C ++에 참조가 추가 된 몇 가지 이유를 나열합니다.

  • 복사 생성자를 정의하는 데 필요합니다
  • 그들은 연산자 과부하에 필요합니다
  • const 참조를 사용하면 복사본을 피하면서 값을 기준으로 의미를 가질 수 있습니다.

그의 주요 요점은 호출 사이트에서 매개 변수가 참조인지 또는 값 매개 변수인지에 대한 표시가 없기 때문에 참조를 '출력'매개 변수로 사용해서는 안된다는 것입니다. 따라서 그의 규칙은 const참조 로만 인수를 사용하는 것입니다.

개인적으로 이것은 매개 변수가 출력 매개 변수인지 아닌지를 명확하게 나타 내기 때문에 이것이 좋은 경험 법칙이라고 생각합니다. 그러나 나는 일반적으로 개인적으로 이것에 동의하지만, 출력 매개 변수를 참조로 주장하는 경우 팀의 다른 사람들의 의견에 흔들릴 수 있습니다 (일부 개발자는 엄청나게 좋아합니다).


8
그 주장에 대한 나의 입장은 함수 이름이 문서를 확인하지 않고 완전히 명확하게 만들면 매개 변수가 수정된다는 것입니다. 그래서 개인적으로 "getDetails (DetailStruct & result)"를 허용합니다. 포인터가 NULL 입력의 추악한 가능성을 높입니다.
Steve Jessop

3
오해의 소지가 있습니다. 일부는 참조를 좋아하지 않더라도 언어의 중요한 부분이므로 그대로 사용해야합니다. 이 추론은 템플릿을 사용하지 말고 항상 void *의 컨테이너를 사용하여 모든 유형을 저장할 수 있습니다. litb로 답변을 읽습니다.
David Rodríguez-dribeas

4
나는 이것이 오도하는 방법을 보지 못합니다-참조가 필요한 경우가 있으며, 모범 사례가 가능하더라도 사용하지 않는 것이 좋습니다. 상속, 비회원 친구, 연산자 오버로드, MI 등 언어의 모든 기능에 대해서도 마찬가지입니다.
Michael Burr

그건 그렇고, 나는 litb의 대답이 매우 좋고, 이것보다 훨씬 포괄적이라는 것에 동의합니다. 나는 출력 매개 변수로 참조를 사용하지 않는 이유에 대해 논의하기로 결정했습니다.
Michael Burr

1
이 규칙은 Google C ++ 스타일 가이드에서 사용됩니다 : google-styleguide.googlecode.com/svn/trunk/…
Anton Daneyko

9

이전 게시물에 대한 설명 :


참조는 널이 아닌 포인터를 보장 하지 않습니다 . (우리는 종종 그것들을 그렇게 취급합니다.)

끔찍한 나쁜 코드이지만, 헛된 나쁜 코드의 뒤를 쫓아가는 것처럼 다음은 컴파일 및 실행됩니다 (적어도 내 컴파일러에서는).

bool test( int & a)
{
  return (&a) == (int *) NULL;
}

int
main()
{
  int * i = (int *)NULL;
  cout << ( test(*i) ) << endl;
};

내가 참조로 가지고있는 진짜 문제는 다른 프로그래머 들에게있다. 따라서 생성자에 할당하고 소멸자에 할당 해제 하고 사본 생성자 또는 연산자 = ()를 제공하지 못하는 IDIOTS 라고 불린다 .

갑자기 foo (BAR bar)foo (BAR & bar) 사이에 차이점이 있습니다. (자동 비트 단위 복사 작업이 호출됩니다. 소멸자의 할당 해제가 두 번 호출됩니다.)

고맙게도 현대 컴파일러는 동일한 포인터의 이중 할당 해제를 선택합니다. 15 년 전에는 그렇지 않았습니다. gcc / g ++에서 setenv MALLOC_CHECK_ 0 을 사용 하여 이전 방법을 다시 방문하십시오. DEC UNIX에서 동일한 메모리에서 두 개의 다른 오브젝트에 할당됩니다. 많은 디버깅 재미가 있습니다 ...


더 실질적으로 :

  • 참조는 다른 곳에 저장된 데이터를 변경하고 있다는 것을 숨 깁니다.
  • 참조를 복사 된 객체와 혼동하기 쉽습니다.
  • 포인터는 그것을 명백하게 만듭니다!

16
그것은 함수 나 참조의 문제가 아닙니다. 언어 규칙을 어 기고 있습니다. 널 포인터 자체를 역 참조하는 것은 이미 정의되지 않은 동작입니다. "참조는 널이 아닌 포인터를 얻는 것을 보장하지 않습니다." 다른 방법은 정의되지 않은 행동을 구성합니다.
Johannes Schaub-litb

1
나는 동의합니다. 사실이지만, 당신이 우리에게 보여준 코드는 다른 것보다 방해가됩니다. "참조"및 "포인터"표기법을 포함하여 모든 것을 방해하는 방법이 있습니다.
paercebal

1
나는 그것이 "목조 적 인 나쁜 코드 뒤에 당신을 꺼내"라고 말했다! 같은 맥락에서 i = new FOO를 가질 수도 있습니다. 내가 삭제; 시험 (* i); 다른 (불행하게도) 매달려있는 포인터 / 참조 발생.
Mr.Ree

1
사실은 아니에요 역 참조 문제 야 NULL을, 오히려 사용 이 역 참조 (널) 개체를. 따라서 언어 구현 관점에서 포인터와 참조 사이에는 실제로 구문 이외의 차이가 없습니다. 기대치가 다른 사용자입니다.
Mr.Ree

2
반환 된 참조로 무엇을하든 관계없이 *i프로그램에는 정의되지 않은 동작이 있습니다. 예를 들어, 컴파일러는이 코드를보고 "OK,이 코드는 모든 코드 경로에서 정의되지 않은 동작을 가지므로이 전체 함수에 도달 할 수 없어야합니다."라고 가정 할 수 있습니다. 그런 다음이 기능으로 이어지는 모든 분기가 수행되지 않는다고 가정합니다. 이것은 정기적으로 수행되는 최적화입니다.
David Stone

5

여기의 대부분의 답변은 의도를 표현하는 관점에서 함수 서명에 원시 포인터를 갖는 본질적인 모호성을 해결하지 못합니다. 문제는 다음과 같습니다.

  • 호출자는 포인터가 단일 객체를 가리키는 지 또는 객체의 "배열"의 시작을 가리키는 지 알 수 없습니다.

  • 호출자는 포인터가 가리키는 메모리를 "소유"하는지 알 수 없습니다. IE, 함수가 메모리를 해제해야하는지 여부 ( foo(new int)-이것이 메모리 누수입니까?).

  • 호출자는 nullptr함수에 안전하게 전달 될 수 있는지 여부를 모릅니다 .

이러한 모든 문제는 참조로 해결됩니다.

  • 참조는 항상 단일 개체를 나타냅니다.

  • 참조는 참조하는 메모리를 소유하지 않으며 단지 메모리에 대한 관점 일뿐입니다.

  • 참조는 null 일 수 없습니다.

이것은 참조가 일반적인 사용을 위해 훨씬 더 나은 후보가됩니다. 그러나 참조가 완벽하지는 않습니다. 고려해야 할 몇 가지 주요 문제가 있습니다.

  • 명시적인 간접 지시가 없습니다. 이것은 &우리가 실제로 포인터를 전달하고 있음을 나타 내기 위해 연산자를 사용해야하기 때문에 원시 포인터의 문제는 아닙니다 . 예를 들어, int a = 5; foo(a);여기서 a가 참조로 전달되고 수정 될 수 있다는 것은 명확하지 않습니다.
  • 무효 성. 이 포인터의 약점은 실제로 참조를 무효화 할 수 있기를 원할 때 강점 이 될 수 있습니다. 로 보는 것은 std::optional<T&>포인터가 우리에게 당신이 원하는 Null 허용을주고, (좋은 이유에 대해) 유효하지 않습니다.

따라서 명시 적 간접 지시가있는 nullable 참조를 원할 때 우리는 T*권리에 도달해야 합니까? 잘못된!

추상화

Null 허용에 대한 절망에서 우리는를 위해 도달 할 수 있으며 T*앞서 나열된 모든 단점과 의미 적 모호성을 무시할 수 있습니다 . 대신, 우리는 C ++이 가장 잘하는 것, 즉 추상화에 도달해야합니다. 포인터를 감싸는 클래스를 작성하면 표현력은 물론 널링 가능성과 명시 적 간접 지시를 얻을 수 있습니다.

template <typename T>
struct optional_ref {
  optional_ref() : ptr(nullptr) {}
  optional_ref(T* t) : ptr(t) {}
  optional_ref(std::nullptr_t) : ptr(nullptr) {}

  T& get() const {
    return *ptr;
  }

  explicit operator bool() const {
    return bool(ptr);
  }

private:
  T* ptr;
};

이것은 내가 생각해 낼 수있는 가장 간단한 인터페이스이지만 효과적으로 작동합니다. 참조를 초기화하고 값이 있는지 확인하고 값에 액세스 할 수 있습니다. 다음과 같이 사용할 수 있습니다.

void foo(optional_ref<int> x) {
  if (x) {
    auto y = x.get();
    // use y here
  }
}

int x = 5;
foo(&x); // explicit indirection here
foo(nullptr); // nullability

우리는 목표를 달성했습니다! 이제 원시 포인터와 비교하여 이점을 살펴 보겠습니다.

  • 인터페이스는 참조가 하나의 객체 만 참조해야한다는 것을 분명히 보여줍니다.
  • 분명히 사용자 정의 소멸자가 없으며 메모리를 삭제하는 방법이 없으므로 참조하는 메모리를 소유하지 않습니다.
  • nullptr함수 작성자가 명시 적으로 요청하기 때문에 호출자는 전달할 수 있음을 알고 있습니다.optional_ref

등식 연산자, 모나 딕 get_ormap인터페이스, 값을 얻거나 예외를 던지는 방법을 추가하는 등의 인터페이스를 여기에서 더 복잡하게 만들 수 constexpr있습니다. 그것은 당신이 할 수 있습니다.

결론적으로, 원시 포인터를 사용하는 대신 해당 포인터가 실제로 코드에서 의미하는 바에 대해 추론하고 표준 라이브러리 추상화를 활용하거나 직접 작성하십시오. 이렇게하면 코드가 크게 향상됩니다.


3

실제로는 아닙니다. 내부적으로 참조로 전달은 참조 된 오브젝트의 주소를 본질적으로 전달하여 수행됩니다. 따라서 포인터를 전달하면 효율성이 향상되지 않습니다.

그러나 참조로 전달하면 한 가지 이점이 있습니다. 전달되는 객체 / 유형의 인스턴스가 보장됩니다. 포인터를 전달하면 NULL 포인터를받을 위험이 있습니다. 통과 기준을 사용함으로써 함축적 인 NULL 검사를 함수의 호출자에게 한 레벨 올립니다.


1
그것은 장점과 단점입니다. 많은 API는 NULL 포인터를 사용하여 유용한 것을 의미합니다 (예 : NULL timespec은 영원히 대기하지만 값은 오래 기다립니다).
Greg Rogers

1
@ 브라이언 : 나는 nit-picking하고 싶지 않지만 참조를 얻을 때 인스턴스를 얻을 수 있다고 보장 하지는 않습니다 . 함수 호출자가 수신자가 알 수없는 댕글 링 포인터를 역 참조하면 댕글 링 참조가 여전히 가능합니다.
foraidt

때로는 참조를 사용하여 성능을 얻을 수도 있습니다. 참조는 스토리지를 필요로하지 않으며 자신에게 할당 된 주소가 없기 때문입니다. 간접적 인 요구가 없습니다.
Johannes Schaub-litb

매달려있는 참조가 포함 된 프로그램은 유효한 C ++이 아닙니다. 따라서 코드 모든 참조가 유효하다고 가정 할 수 있습니다 .
Konrad Rudolph

2
분명히 null 포인터를 역 참조 할 수 있고 컴파일러는 말할 수 없습니다 ... 컴파일러가 "유효하지 않은 C ++"이라고 말할 수 없다면 실제로 유효하지 않습니까?
rmeador
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.