호출되는 함수에 매개변수가 존재한다면, 함수 호출 때의 값(argument)이 호출되는 함수의 매개변수(parameter)로 전달됩니다. 자료 전달은 값으로 전달(pass-by-value), 참조로 전달(pass-by-reference), 포인터로 전달(pass-by-pointer)의 3가지 메커니즘으로 구분합니다.
1. 값으로 전달(pass-by-value)
인수(argument)의 값이 복사되어서 매개변수(parameter)에 할당됩니다. 이는 호출되는 함수 쪽에서 인수를 변경하지 않게 만들고 싶을 때 사용되며 ‘읽기 전용 접근(read-only access)’라고 표현하기도 합니다.
/**************************************************************
* 값으로 전달 메커니즘에서 parameters의 변경이 *
* arguments에 영향을 주지 않는다는 것을 확인하는 프로그램 *
**************************************************************/
#include <iostream>
using namespace std;
void fun(int y);
int main()
{
int x = 10;
fun(x);
cout << "main 함수 내부의 x = " << x << endl; // 10 출력됨
return 0;
}
void fun(int y)
{
y++;
cout << "fun 함수 내부의 y = " << y << endl; // 11 출력됨
return;
}
위 예시의 경우 fun 함수는 x 값을 전달받고 이를 y에 저장합니다. x와 y는 서로 다른 메모리 위치를 가지므로 fun 함수의 y를 증가시켜도 main 함수의 x에는 영향이 없습니다.
이는 인수의 값을 복사해서 매개변수로 전달하므로 전달해야 하는 값이 크다면 복사하는 작업이 무거워질 수 있습니다. 그러므로 객체의 크기가 클 때 값으로 전달 메커니즘을 사용하지 않습니다.
2. 참조로 전달(pass-by-reference)
인수와 매개변수는 메모리 위치를 공유합니다.
컴파일러에게 매개변수 y를 위한 별도의 메모리 영역을 선언하지 말라고 알려주기 위해서, 변수를 새로 선언(int y)하지 말고 변수를 별칭으로 선언(int& y)하겠다고 매개변수에 표기해야 합니다.
/**************************************************************
* 참조로 전달 메커니즘에서 parameter의 변경이 *
* argument에 영향을 준다는 것을 확인하는 프로그램 *
***************************************************************/
#include <iostream>
using namespace std;
void fun(int& y); // & 기호는 y가 참조라는 것을 나타냄
int main()
{
int x = 10;
fun(x);
cout << "main 함수 내부의 x = " << x << endl; // 11 출력됨
return 0;
}
void fun(int& y)
{
y++;
cout << "fun 함수 내부의 y = " << y << endl; // 11 출력됨
return;
}
fun 함수는 y로 참조를 전달받아 y는 함수 호출 때 argument의 별칭이 됩니다. 그러므로 parameter를 변경하면 argument가 함께 변해 둘 다 11을 출력합니다.
이를 ‘읽고 쓸 수 있는 접근(read-write access)’라고 표현하기도 합니다. 복사가 필요하지 않다는 장점이 있습니다.
3. 포인터로 전달(pass-by-pointer)
인수로 메모리 주소를 매개변수에 전달합니다.
주소를 전달하므로 매개변수를 사용해서 인수의 메모리 위치에 접근할 수 있습니다. 참조로 전달 메커니즘과 같은 장점을 가지며, 일반적으로 C++에서는 많이 사용하지 않지만 전달해야하는 자료가 포인터의 특성을 갖고 있을 경우에는 포인터로 전달 메커니즘을 사용합니다.
값에 의한 전달보다 참조에 의한 전달이 더 나은 이유는 무엇인가?
객체가 값으로 전달되면 객체의 새로운 사본이 생성되어 함수에 전달됩니다. 이는 굉장히 큰 벡터 객체를 전달하는 경우 사본을 생성하는 데 비용이 많이 듭니다. 그래서 클래스가 복사 생성자를 private로 선언하는 등의 방법을 통해 클라이언트가 복사본을 생성하지 못하게 하는 경우도 있습니다.
참조로 전달하면 함수는 객체의 주소를 받아 목적에 따라 객체를 수정할 수 있습니다. 이를 못하게 하려면 const 참조로 객체를 전달해야 합니다.
값으로 전달할 때 발생 가능한 또 다른 문제는 원하지 않는 암시적 변환이 일어날 수 있다는 점입니다. 예를 들면 파생 클래스가 다형성을 방지하는 기본 클래스로 캐스팅될 수도 있습니다.
class Base {
public:
virtual string msg() { return "I am base"; }
};
class Child : public Base {
public:
virtual string msg() { return "I am child"; }
};
void cast(Base x) { cout<< x.msg() << endl; } // 값에 의한 전달
int main() {
Base f;
Child b;
cast(f);
cast(b);
}
모두 "I am base"를 출력합니다.
값에 의한 전달로 인해 Child 객체를 cast()에 전달할 때, 매개변수 Base x로 복사되어 이 복사본의 타입은 항상 Base로 고정됩니다. 따라서 다형성이 무력화되어 Child 객체의 msg()가 아니라, Base 객체의 msg()가 호출됩니다.
void cast(Base& x) { cout<< x.msg() << endl; } // 참조에 의한 전달
이렇게 수정하면 첫 번째 호출은 "I am base"를, 두 번째 호출은 "I am child"를 출력하게 됩니다.
출처 : 포르잔 C++ 바이블, 266가지 문제로 정복하는 코딩 인터뷰 in C++
'C++' 카테고리의 다른 글
[C++] 반복자(iterator) (1) | 2025.01.02 |
---|