소년코딩

08.03 - 참조로 전달 (Pass by reference)

값으로 전달은 두 가지 한계가 있다. 첫째, 큰 구조체 또는 클래스를 함수에 전달할 때 값으로 전달은 인수의 복사본을 함수 매개 변수로 만든다. 이 경우 복사하는데 큰 비용이 들어 성능이 저하될 수 있다. 둘째, 값으로 인수를 전달할 경우 함수에서 호출자에게 값을 반환하는 유일한 방법은 함수의 반환 값을 사용하는 것이다. 이 방법도 좋지만, 함수에서 인수를 수정하는 것이 더 명확하고 효율적일 수 있다. 참조를 통해 위 두 가지 문제를 해결할 수 있다.

변수를 참조로 전달하려면 함수 매개 변수를 일반 변수가 아닌 참조로 선언해야 한다.

void addOne(int& y) // y is a reference variable
{
    y = y + 1;
}

함수가 호출되면 y는 인수에 대한 참조가 된다. 변수에 대한 참조는 변수 자체와 똑같이 취급되므로 참조에 대한 모든 변경 사항은 인수에도 적용된다.

다음 예제는 위 내용을 보여준다.

void foo(int& value)
{
    value = 6;
}

int main()
{
    int value = 5;

    cout << "value = " << value << '\n';
    foo(value);
    cout << "value = " << value << '\n';
    return 0;
}

프로그램에서 foo() 함수의 매개 변수가 일반 변수 대신 참조가 된다는 점을 제외하고는 이전 포스트 '값으로 전달' 예제에서 사용한 것과 똑같다. foo(x)를 호출하면 yx에 대한 참조가 된다. 그래서 다음과 같이 출력한다.

value = 5
value = 6

보시다시피 함수는 인수의 값을 5에서 6으로 변경했다!

여기 또 다른 예제가 있다.

void addOne(int& y) // y is a reference variable
{
    y = y + 1;
} // y is destroyed here

int main()
{
    int x = 5;
    std::cout << "x = " << x << '\n';
    addOne(x);
    std::cout << "x = " << x << '\n';
    return 0;
}

/*
x = 5
x = 6
*/

인수 x의 값은 함수에 의해 변경되었다.


const 참조로 전달 (Pass by const reference)

참조를 사용하면 함수가 인수의 값을 변경할 수 있으므로 인수가 읽기 전용일 때는 사용하기에 바람직하지 않다. 함수가 인수의 값을 변경해서는 안 되지만 값으로 전달하지 않으려면 const 참조를 전달하는 것이 가장 좋다.

const 참조는 변수가 참조를 통해 변경되는 것을 허용하지 않는 참조다. 그러므로 const 참조를 매개 변수로 사용하면 함수가 인수를 변경하지 않는다는 것을 호출자에게 보장한다!

다음 함수는 컴파일러 오류를 생성한다.

void foo(const int& x) // x is a const reference
{
    x = 6;  // compile error: const 참조는 값을 변경할 수 없다.
}

const 참조를 사용하는 것은 다음과 같은 이유로 유용하다.

  • 컴파일러는 변경해서 안 되는 값을 변경하지 않도록 한다. (위의 예제처럼 시도할 경우 오류가 발생한다.)
  • 함수가 인수의 값을 변경하지 않는다는 것을 프로그래머에게 알려준다. (디버깅에 도움이 될 수 있다.)
  • const가 아닌 참조 매개 변수에는 const 인수를 전달할 수 없다. const 매개 변수를 사용하면 non-const 및 const 인수를 함수에 전달할 수 있다.
  • const 참조는 l-value, const l-value 및 r-value를 포함한 모든 유형의 인수를 허용할 수 있다.

규칙: 참조로 인수를 전달할 때 인수 값을 변경해야 하는 경우가 아니면 항상 const 참조를 사용하자.


포인터에 대한 참조 (References to pointers)

포인터를 참조로 전달하고 함수가 포인터의 주소를 완전히 변경하도록 할 수 있다.

#include <iostream>

void foo(int*& ptr) // pass pointer by reference
{
    ptr = nullptr; // 실제 ptr 인수를 변경할 수 있다.
}

int main()
{
    int x = 5;
    int *ptr = &x;
    std::cout << "ptr is: " << (ptr ? "non-null" : "null") << '\n'; // prints non-null
    foo(ptr);
    std::cout << "ptr is: " << (ptr ? "non-null" : "null") << '\n'; // prints null

    return 0;
}

참조를 이용해서 C 스타일 배열을 주소별로 전달할 수 있다. 함수가 배열을 변경하거나 고정 배열의 배열 정보에 접근해야 하는 경우 유용하다. 그러나 이 작업을 수행하려면 매개 변수에서 배열 크기를 명시적으로 정의해야 한다.

#include <iostream>

// Note: You need to specify the array size in the function declaration
void printElements(int (&arr)[4])
{
  int length{ sizeof(arr) / sizeof(arr[0]) }; // we can now do this since the array won't decay

  for (int i{ 0 }; i < length; ++i)
  {
    std::cout << arr[i] << std::endl;
  }
}

int main()
{
    int arr[]{ 99, 20, 14, 80 };

    printElements(arr);

    return 0;
}

참조로 전달의 장단점 (Pros and cons of pass by reference)

참조로 전달의 장점:

  • 참조를 사용하면 함수가 인수의 값을 변경할 수 있다. 또한 const 참조를 사용해서 함수가 인수를 변경하지 않는다는 것을 보장할 수 있다.
  • 인수의 복사본이 만들어지지 않으므로 큰 구조체나 클래스와 함께 사용하는 경우에도 전달이 빠르다.
  • 참조는 함수에서 매개 변수를 통해 여러 값을 반환할 수 있다.
  • 참조는 초기화되어야 하므로 null 값에 대해 걱정할 필요가 없다.

참조로 전달의 단점:

  • non-const 참조는 const 값 또는 r-value(리터럴 또는 표현식)으로 초기화할 수 없으므로 참조 매개 변수에 대한 인수는 일반 변수여야 한다.
  • 인수가 변경될 수 있는지는 함수 호출에서 알 수 없다. 값으로 전달이나 참조로 전달 모두 인수에서는 동일하게 보인다. 함수 선언을 보고 인수가 값으로 전달인지 참조로 전달인지 알 수 있다. 이것은 프로그래머가 함수에서 인수의 값을 변경한다는 것을 인식하지 못하는 상황을 초래할 수 있다.

참조로 전달을 사용해야 하는 경우:

  • 구조체 또는 클래스를 전달할 때 (읽기 전용인 경우 const 사용)
  • 인수를 수정하는 함수가 필요할 때
  • 고정 배열의 유형 정보에 접근해야 할 때

참조로 전달을 사용하지 않아야 하는 경우:

  • 수정할 필요가 없는 기본 자료형을 전달할 때 (값으로 전달 사용)

cpp 번역: 이 포스트의 원문은 http://www.learncpp.com/cpp-tutorial/73-passing-arguments-by-reference/ 입니다.

댓글 로드 중…

블로그 정보

소년코딩 - 소년코딩

소년코딩, 자바스크립트, C++, 물리, 게임 코딩 이야기

최근에 게시된 이야기