const 포인터를 시작하기 전에...
우선 이번 포스팅은 아마 C++에서도 가장 헷갈리는 파트중의 하나가 아닐까 싶습니다. const 와 pointer 가 마구 나와서 쓰다보면 이게 맞는지??를 확인하면서 작성하다가 다시 원점으로 돌아가게 되는 참으로 혼동스러운 부분이 있습니다.
이렇게 헷갈리는 내용은 한번에 이해하기 힘들지만 빙둘러가지 않고 단도직입 적으로 코드에 들어가보는게 더 나을 때가 있습니다. 이 페이지는 아래의 코드를 이해하기 위한 내용입니다.
[왜? 사용하는가?]
이 전의 포스팅에서 객체를 함수에 전달할 때 포인터를 사용하는 장단점에 대하여 해설했습니다.
C++ | 객체를 함수에 전달하는 방법 | passing object by value , reference pointer (tistory.com)
pass by value 와 pass by reference using pointer 방식의 차이죠.
프로그램의 기계적인 효율은 포인터가 더 좋다고 했습니다. 그러나 단점이 크죠. 포인터를 사용하면 원본에 접근이 가능하므로 의도치 않게 포인터와 역참조값을 변경할 가능성이 있습니다. const 포인터를 사용하면 포인터를 변경하지 못하게 제한을 둘 수 있습니다.
▶ const MyPosition* 은 객체에 포인터를 선언하면 const 메소드만 호출할 수 있습니다.
▶ const myPos 는 포인터를 바꾸지 못하게 합니다.
아래의 함수를 보겠습니다. 너무 const 가 많이 붙어있는데 다른 것 보다는 헤드의 매개변수를 집중해서 봅니다.
getXPos()는 클래스에서 const 키워드가 붙어있는 상수 메소드로 사용할 수 있습니다. 상수 메소드 - const method 의 경우 그 객체 내부의 멤버를 변경할 수 없습니다.
주석처리한 부분을 보면 myPos 포인터는 변경할 수 없습니다. 주석을 풀면 오류를 발생합니다.
setXPos도 const 메소드가 아닙니다. const 키워드가 붙어있지 않지만 내부에서 멤버 값을 변경하고 있으니 당연히 안되죠. 따라서 여기도 주석처리했습니다.
[장단점]
const 포인터롤 객체를 전달하면 copy constructor 를 호출하지 않으며, 데이터의 안정성까지 프로그래머가 통제할 수 있다는 장점이 있습니다. 단점은 역시 다루기가 어려우며 학습시간도 오래걸리겠지만 실제 작업시간이 더 걸리겠죠. 내용이 어려우면 타인과 소통하는데도 그 만큼 시간이 걸립니다.
앞으로 C++ 프로그래밍을 하면서 const 를 사용한 라이브러리 함수를 많이 보게 될 것입니다. 좋은 라이브러리를 많이 써보고 스타일을 참조하면 좋습니다.
객체에 대한 const 포인터를 함수의 매개변수로 넘길 때의 사항에 대하여 알아봤습니다. 헷갈리고 쉽고 어려운 내용이 있습니다. 처음에 이해가 안된다고 너무 좌절하지 말고 하나의 교재를 가지고 두바퀴, 세바퀴 공부하다 보면 쉬워지는 날이 옵니다.
실수를 두려워하지 말고 꾸준히 코드를 실행시키고 참조 문서를 읽다보면 반드시 실력이 좋아질 것 입니다.
*링크 - const 포인터:
C++ | 포인터 | const pointer (상수 포인터) (tistory.com)
예제의 코드 중에 함수 부분을 집중해서 보고 객체의 메소드와 멤버변수들과 어떤 관계가 맺어져 있는지를 확인합니다. 객체지향 프로그래밍의 코드는 위에서 부터 읽는게 아니라 main 함수의 실행을 따라가면서 객체와 왔다갔다 하면서 연관성을 찾아가며 읽습니다.
눈으로 훑더라도 흐름을 파악할 수 있으면 헛되지 않습니다.
* 예제코드
#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() const {
// this->xPos = 999;
return this->xPos;}
void setYPos(int y) { this->yPos = y; }
int getYPos() const { return this->yPos; }
private:
int xPos;
int yPos;
};
MyPosition::MyPosition()
{
xPos = 50;
yPos = 70;
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;
}
const MyPosition* const MyFunction
(const MyPosition* const myPos);
int main()
{
Line;
cout << " -> making my position..." << endl;
MyPosition myPos;
cout << " @ my position x : " << myPos.getXPos() << endl;
cout << " @ my position y : " << myPos.getYPos() << endl;
myPos.setXPos(100);
myPos.setYPos(150);
Line;
cout << " @ my position x : " << myPos.getXPos() << endl;
cout << " @ my position y : " << myPos.getYPos() << endl;
Line;
MyFunction(&myPos);
return 0;
}
const MyPosition* const
MyFunction(const MyPosition* const myPos)
{
cout << " -> MyFunction returning... at : " << myPos << endl;
cout << " -> getXpos() "<< myPos->getXPos() << endl;
cout << " -> getYpos() "<< myPos->getYPos() << endl;
// is not a l-value! can't change the value inside the function.
// myPos = NULL;
// const error! can't change the setter
// myPos->setXPos(52);
return myPos;
}
레퍼런스를 사용하는 경우는 함수의 헤드를 바꾸고 -> 역참조 연산자를 도트연산자로 바꾼다. 문법적인 사항에 얽매이기 보다는 레퍼런스와 포인터의 차이가 무엇인지를 생각한다.
▶ 레퍼런스는 null 이 될 수 없다. 포인터에서는 const 키워드를 사용했고 레퍼런스는 const 를 사용할 필요가 없다.
const MyPosition& MyFunction(const MyPosition& myPos)
{
cout << " -> MyFunction returning... at : " << &myPos << endl;
cout << " -> getXpos() "<< myPos.getXPos() << endl;
cout << " -> getYpos() "<< myPos.getYPos() << endl;
// is not a l-value! can't change the value inside the function.
// myPos = NULL;
// const error! can't change the setter
// myPos.setXPos(52);
return myPos;
}
호출시에는 그대로 호출하면 된다.
MyFunction(myPos);
레퍼런스의 호출도 마찬가지로 copy constructor 를 생성하지 않는다.
[어떤 경우에 포인터 / 레퍼런스 를 사용해야 하는가?]
C++의 철학은 기본적으로 프로그래머를 신뢰한다. 좋게 말하면 신뢰이고 안좋게 말하면 무책임한 것이다. 최근 언어의 트렌드는 컴파일러와 런타임이 알아서 챙겨주는 동적타입(dynamic type) 언어들의 흐름이 명확하다. 최근의 흐름에서 봤을 때 C++은 좀 무책임한 부분이 있다. 논쟁거리로는 충분하나 이런 주제에 너무 에너지를 쏟는 것은 언어를 개발하는 시스템 엔지니어가 아니라면 그렇게 생산적이지 않다.
포인터 / 레퍼런스 어느쪽을 쓰더라도 상관이 없는데 일반적으로 레퍼런스가 사용하기 쉽고 const 처럼 작동하니까 레퍼런스의 사용을 선호한다. 다만 pointer 를 변경시켜야 하는 경우네는 레퍼런스를 사용할 수 없다. pointer 에 const 를 걸면 레퍼런스와 똑같아지긴 하는데... 그래서 선택할 자유를 준다고 하는 것이다. C++ 프로그래머는 좀 더 능동적인 판단을 할 수 있다.