이번 포스팅은 객체를 함수에 전달한다는 조금 어려운 개념에 대한 내용입니다.
C++로 이 정도 챕터까지 학습을 진행했다면 밑에서 부터 기초를 착실히 쌓아둬야 그 다음 학습이 수월합니다.
다른 언어와 비교해 봤을 때 C++의 학습시간이 길어지는 것은 포인터와 연관한 주제들에 대하여 다루는 언어가 C++밖에 없기 때문입니다. C언어에도 있지만 C언어에는 객체지향 부분이 빠져 있으니까 C++의 부분집합으로 볼 수 있습니다. C++의 학습량이 훨씬 많죠.
기본 자료형을 함수에 전달하는 것 처럼 객체 타입 역시 함수의 매개변수에 전달할 수 있습니다.
매개변수에 있어서 일반 변수와 포인터 전달방식의 차이를 이해한다면 객체를 전달하는 방식도 이해할 수 있습니다.
원리는 동일합니다. 값에 의한 전달 (pass by value)로 객체를 전달하면 스택에 복사본을 생성합니다. 이 때 C++ 의 클래스에서는 copy constructor 라는 특별한 생성자를 호출합니다. 스택에 생성되었으니 지역변수가 소멸될 때(함수가 종료할 때) 같이 사라집니다.
아래 코드는 MyPosition 클래스의 copy constructor 입니다. 매개변수의 객체에 & 연산자를 사용하고 있습니다.
아래 함수를 호출할 때 copy constructor를 호출합니다. 명시되어 있지는 않지만 자동으로 호출합니다. copy constructor 를 총 두번 호출하는데 한 번은 매개변수를 복사할 때이고 또 한번은 return 문에서 값을 반환할 때 입니다.
함수를 한번 호출하는데 두번이나 생성자를 호출하는 것은 메모리 사용량과 속도면에서 포인터를 이용한 방법보다 효율적이지 않습니다.
pass object by value 방식은 아래와 같이 함수 호출 하나에 두번의 copy constructor 와 destructor 를 호출해야합니다.
포인터를 전달하면 객체를 직접 사용할 수 있습니다.
아래 예제코드는 매개변수를 포인터로 선언합니다. 반환값도 포인터로 선언하지만 여기서 큰 의미는 없습니다. 포인터를 전달하면 객체의 내용을 함수내에서 직접 변경할 수 있습니다.
있는 객체를 전달하는 것이기 때문에 생성자와 소멸자는 호출되지 않습니다. 이 부분이 객체를 매개변수로 사용할 때 값에 의한 전달과 차이가 큰 부분이죠. 매개변수에서 반환값까지 생성자 호출이 두번, 소멸자 호출이 두번 줄어듭니다.
객체를 값으로 전달하거나 포인터로 전달하거나 실행결과는 같습니다. 하지만 과정의 차이는 분명합니다. 값에 의한 전달방식은 객체를 스택에 복사하는 과정에 copy constructor 를 호출합니다. 객체에 대한 포인터를 사용하면 copy constructor를 호출할 필요가 없죠. 대신 포인터의 사용과 관리가 훨씬 어렵다는 특징이 있습니다.
C++을 사용하는 이상 포인터의 장점을 최대한 활용하는 쪽이 좋습니다. C++에서는 여러가지 부분에서 프로그래머에게 최대한의 선택권을 부여합니다. 안좋게 말하면 '당신이 알아서 책임을 져야해~' 이고 좋게 말하면 '프로그래머의 자율성을 존중한다' 라고 해석할 수 있습니다. 우리 인생처럼 양면성이 있죠.
물컵에 물이 반이나 들어있는 것과 반밖에 없는 것의 차이가 사물은 그대로이지만 그것을 바라보는 사람의 관점의 차이에 있습니다. C++의 포인터를 해석할 때는 가급적 긍정적인 시각으로 바라보는게 좋습니다. 어차피 포인터는 포인터입니다. 나쁘게 생각하면 한없이 나쁠 것이고 장점을 생각하면 끝이 없을 것 입니다.
게다가 이제는 포인터를 다루지 않아도 되는 언어들이 많습니다. C++에 지치면 잠시 내려놓고 파이썬이나 자바스크립트로 작은 프로젝트라도 하면서 기분전환하는 것도 좋습니다. 코딩이 몇단계 수월하게 느껴질 테니까요. 온도차를 느끼는 경험도 꽤 신선합니다.
*예제코드
#include <iostream>
using namespace std;
#define Line cout <<"\n-----------------------------\n"
class MyPosition {
public:
MyPosition();
MyPosition(MyPosition&);
~MyPosition();
void setXPos(int x) { this->xPos = x; }
int getXPos() {return this->xPos;}
void setYPos(int y) { this->yPos = y; }
int getYPos() { return this->yPos; }
private:
int xPos;
int yPos;
};
MyPosition::MyPosition()
{
xPos = 0;
yPos = 0;
cout << " [basic constructor called..] ";
cout << " address: " << this << endl;
}
MyPosition::MyPosition(MyPosition&)
{
cout << " [copy constructor called...] ";
cout << " address: " << this << endl;
}
MyPosition::~MyPosition()
{
cout << " [desctrutor called... ] ";
cout << " address: " << this << endl;
}
MyPosition function1(MyPosition myPos);
MyPosition* function2(MyPosition* myPos);
int main()
{
Line;
cout << " -> making my position..." << endl;
MyPosition myPos;
cout << " -> calling function 1 ..." << endl;
Line;
function1(myPos);
cout << " -> calling function 2 ..." << endl;
function2(&myPos);
return 0;
}
MyPosition function1(MyPosition myPos)
{
cout << " -> function1 returning..." << endl;
return myPos;
}
MyPosition* function2(MyPosition *myPos)
{
cout << " -> function2 returning..." << endl;
return myPos;
}