[C++] 메모리 접근, 클래스 멤버 접근 연산자 오버로딩 (*, ->)

Seongcheol Jeon·2025년 7월 9일
0

CPP

목록 보기
45/47
post-thumbnail

메모리 접근, 클래스 멤버 접근 연산자 (*, -> 연산자)

*, -> 연산자는 스마트 포인터반복자(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, p2Point 클래스의 멤버를 접근할 수 있도록 -> 연산자오버로딩해야 한다.

#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()처럼 함수를 호출한다.

0개의 댓글