*, ->
연산자는 스마트 포인터
나 반복자(iterator)
등의 특수한 객체에 사용된다. 반복자
가 STL의 핵심 구성 요소이므로 *, ->
연산자 오버로딩이 아주 ❗중요 하다.
스마트 포인터
는 일반 포인터의 기능에 몇 가지 유용한 기능을 추가한 포인터처럼 동작하는 객체이다.
다음은 일반 포인터를 사용하여 Point 클래스의 멤버 함수를 호출하는 예제이다. 이렇게 일반 포인터를 사용하면 new
연산 후 delete
연산을 호출하지 않으면 메모리 누수
가 발생하여 프로그램에 심각한 문제가 된다. 또한, 사용 중에 함수가 종료하거나 예외 등이 발생하면 동적으로 할당한 메모리를 해제하지 못하는 문제가 발생한다. 이런 문제들은 스마트 포인터
를 사용하여 쉽게 해결할 수 있다.
#include <iostream>
using namespace std;
class Point
{
public:
Point(int x = 0, int y = 0) : x(x), y(y) {}
void Print() const { cout << x << ", " << y << endl; }
private:
int x;
int y;
};
int main()
{
Point* p1 = new Point(1, 2);
Point* p2 = new Point(3, 4);
p1->Print();
p2->Print();
delete p1;
delete p2;
return 0;
}
일반 포인터 p1
, p2
는 가리키는 동적 객체
를 delete
연산으로 직접 제거 해야 한다.
다음은 PointPtr
클래스의 소멸자를 이용해 동적으로 할당된 Point 객체(heap 객체)
를 자동으로 제거한다.
#include <iostream>
using namespace std;
class Point
{
public:
Point(int x = 0, int y = 0) : x(x), y(y) {}
void Print() const { cout << x << ", " << y << endl; }
private:
int x;
int y;
};
class PointPtr
{
public:
PointPtr(Point* p) : ptr(p) {}
~PointPtr() { delete ptr; }
Point* ptr;
};
int main()
{
PointPtr p1 = PointPtr(new Point(1, 2));
PointPtr p2 = PointPtr(new Point(3, 4));
p1.ptr->Print();
p2.ptr->Print();
return 0;
}
이렇게 PointPtr
클래스를 만들어 놓으면 동적
으로 생성한 Point 객체
를 소멸자
에서 자동
으로 삭제하므로 프로그램 중에 예외가 발생하거나 delete
호출을 빼먹어 발생하는 동적 메모리 누수 현상
을 방지할 수 있다.
p1
, p2
는 스택
객체이므로 main()
함수 블록에서 제거되며, 이때 p1
, p2
의 소멸자에서 ptr
이 가리키는 동적 메모리 객체를 제거(delete)
한다.
p1
, p2
가 일반 포인터처럼 동작하려면, p1
, p2
로 Point 클래스에 정의도니 멤버 함수를 사용할 수 있어야 한다.
이때 PointPtr
객체 p1
, p2
로 Point
클래스의 멤버를 접근할 수 있도록 -> 연산자
를 오버로딩
해야 한다.
#include <iostream>
using namespace std;
class Point
{
public:
Point(int x = 0, int y = 0) : x(x), y(y) {}
void Print() const { cout << x << ", " << y << endl; }
private:
int x;
int y;
};
class PointPtr
{
public:
explicit PointPtr(Point* p) : ptr(p) {}
~PointPtr() { delete ptr; }
Point* operator->() const
{
return ptr;
}
private:
Point* ptr;
};
int main()
{
PointPtr p1 = PointPtr(new Point(1, 2));
PointPtr p2 = PointPtr(new Point(3, 4));
p1->Print();
p2->Print();
return 0;
}
p1->Print()
는 p1.operator->()
함수를 호출해 p1
내부에 보관된 실제 포인터를 반환받고 이 포인터를 이용해 실제 Point
의 멤버 함수(p1.operator->()->Print())
를 호출한다.
또한, 일반 포인터의 * 연산자
는 포인터가 가리키는 객체 자체이므로 스마트 포인터에도 * 연산이 가능하도록 연산자 오버로딩한다.
#include <iostream>
using namespace std;
class Point
{
public:
Point(int x = 0, int y = 0) : x(x), y(y) {}
void Print() const { cout << x << ", " << y << endl; }
private:
int x;
int y;
};
class PointPtr
{
public:
explicit PointPtr(Point* p) : ptr(p) {}
~PointPtr() { delete ptr; }
Point* operator->() const
{
return ptr;
}
Point& operator*() const
{
return *ptr;
}
private:
Point* ptr;
};
int main()
{
Point* p1 = new Point(1, 2); // 일반 포인터
PointPtr p2 = PointPtr(new Point(3, 4)); // 스마트 포인터
p1->Print(); // 일반 포인터 사용
p2->Print(); // 스마트 포인터 사용
cout << endl;
(*p1).Print(); // 일반 포인터 사용 :: (*p1).Print();
(*p2).Print(); // 스마트 포인터 사용 :: p2.operator*().Print();
delete p1; // 일반 포인터는 직접 메모리 해제 필요
return 0;
}
p1
은 일반 포인터로 *p1
연산이 객체 자체이므로 (*p1).Print()
와 같이 멤버 함수를 호출한다. p2
는 스마트 포인터로 *p2
연산이 객체를 반환하게 p2.operator*()
를 호출하고 객체 참조를 받아 p1.operator*().Print()
처럼 함수를 호출한다.