Minwoo Dev.

[C++] operator new, operator new[] 본문

C++

[C++] operator new, operator new[]

itisminu 2024. 7. 23. 11:05
728x90
반응형
SMALL

이번에는 new 연산자를 오버로딩 해보겠다.

 

우선, new 연산자의 역할을 먼저 알아보자면 아래와 같다.

  • 메모리 공간의 할당
  • 생성자의 호출
  • 할당하고자 하는 자료형에 맞게 반환된 주소 값의 형 변환

 

위 세 가지 역할 중에서 우리는 메모리 공간의 할당에 해당되는 부분만 오버로딩을 할 수 있다.

다른 두 가지는 컴파일러의 역할이기 때문이다.

 

 

메모리 공간의 할당에 대한 순서를 알아보면,

  1. 필요한 메모리 공간을 계산
  2. 계산된 크기의 값을 인자로 전달하여 operator new 호출

 

operator new

우선 new 연산자 오버로딩에서는 필수적으로 지켜저야 할 것이 있다.

  • 매개변수로 들어가는 인자는 size_t 이어야 한다.
  • 반환형은 void * 이어야한다.
  • 인자로 전달하는 size는 바이트 단위로 전달되어야 한다.

위 조건을 지켜서 new 연산자의 틀을 작성해보면,

void * operator new(size_t size){
    void* adr = new char[size];
    return adr;
}

 

위와 같은 형식이 기본이 된다.

왜 new char[size]에서 char으로 동적 할당을 수행하냐고 궁금증이 생길 수 있다.

 

C++에서 char 자료형의 메모리 크기가 얼마인지 아는가 ?

바로 1바이트 이다.

 

위 조건에서 인자로 전달하는 size는 바이트 단위로 전달되어야 한다고 하였기에, 1바이트를 사용하는 char을 이용해 동적 할당을 수행한 것이다.

 

 

이제 실제 클래스 내부에 포함시킨 예제 코드를 살펴보겠다.

#include<iostream>
using namespace std;

class Point{
private:
    int xpos, ypos;
public:
    Point(int x=0,int y=0):xpos(x),ypos(y){}
    friend ostream &operator<<(ostream &os, const Point &pos);

    void * operator new (size_t size){ // new 연산자 오버로딩
        cout << "operator new : " << size << endl;
        void *adr = new char[size];
        return adr;
    }
    void operator delete(void * adr){
        cout << "operator delete ()" << endl;
        delete[] adr;
    }
};

ostream& operator<<(ostream& os, const Point& pos){
    os << '[' << pos.xpos << ", " << pos.ypos << ']' << endl;
    return os;
}

int main(void){
    Point *ptr = new Point(3, 4);
    cout << *ptr;
    delete ptr;
    return 0;
}

 

실행 결과

 

위 코드에서의 new 연산자 오버로딩은 operator new의 사이즈를 출력하는 코드를 추가하는 방식으로 오버로딩을 진행하였다.

 

참고로, size_t size에는 계산된 바이트의 크기가 들어가게 된다.

int형 변수 두 개가 있으므로 게산된 바이트의 크기는 4*2 = 8인 것이다.

 

이렇듯 new 연산자의 오버로딩은 기존에 있던 틀을 유지하되, 다른 것들을 추가할 수 있다는 방식이다.

 

 

operator new[]

컴파일러가 바이트를 계산하여 매개변수로 넘겨주기 때문에, new[] 연산자는 new 연산자의 오버로딩과 내용이 같다.

void * operator new[](size_t size){
    void* adr = new char[size];
    return adr;
}

 

컴파일러가 size를 계산하여 넣어주기 때문에 함수의 내용은 바꾸지 않아도 된다.

 

예제 코드를 보자.

#include<iostream>
using namespace std;

class Point{
private:
    int xpos, ypos;
public:
    Point(int x=0, int y=0):xpos(x),ypos(y){}
    friend ostream &operator<<(ostream &os, const Point &pos);

    void * operator new (size_t size){ // 기존의 new 연산자 오버로딩
        cout << "operator new : " << size << endl;
        void *adr = new char[size];
        return adr;
    }
    void * operator new[] (size_t size){ // new[] 연산자 오버로딩
        cout << "operator new [] : " << size << endl;
        void *adr = new char[size];
        return adr;
    }

    void operator delete(void * adr){
        cout << "operator delete() " << endl;
        delete[] adr;
    }

    void operator delete[](void * adr){
        cout << "operator delete[] () " << endl;
        delete[] adr;
    }
};

ostream& operator<<(ostream& os, const Point& pos){
    os << '[' << pos.xpos << ", " <<pos.ypos<< ']' << endl;
    return os;
}

int main(void){
    Point *ptr = new Point(3, 4);
    Point *arr = new Point[3];
    delete ptr;
    delete[] arr;
    return 0;
}

 

 

실행 결과

 

 

size를 컴파일러가 계산하여 매개변수로 전달하기 때문에 new 연산자 오버로딩 함수와 new [] 연산자 오버로딩 함수의 내용이 똑같음에도 도 size가 다르게 입력되어 원하는 대로 결과가 출력되는 것을 확인할 수 있다.

 

 

 

 

정리하자면, new 연산자는 메모리 공간의 할당만 오버로딩으로 사용할 수 있으며, 정해진 형식(void* 반환, 매개변수 size_t)를 지켜서 작성해야하고, 컴파일러가 크기를 계산하여 size에 매개변수로 넣어준다는 점이다.

728x90
반응형
LIST