Language/C++

📌 Chapter 10 - 나머지 연산자 오버로딩

e-cko 2025. 9. 16. 17:16
반응형
반응형

1. new 연산자 오버로딩

1) new 연산자의 기본 개념

  • new 연산자는 다음의 작업을 수행한다.
    ·
    메모리 공간의 할당
    ·
    생성자 호출
    ·
    자료형에 맞는 주소 값 형 변환
  • new 연산자를 오버로딩할 경우, 메모리 공간의 할당을 직접 책임져야 한다.
  • 반환형은 반드시 void* 형이어야 한다.
  • 매개변수는 size_t 형을 사용한다.
    size_t typedef unsigned int size_t;로 정의된 자료형이며, 0 이상의 정수를 표현한다.
  • new 연산자가 호출되면:
    1️
    필요한 메모리 크기를 계산한다.
    2️
    operator new() 함수가 호출되고, 계산된 크기를 인자로 전달한다. (단위: byte)
    3️
    operator new()가 주소값을 반환하면, 컴파일러가 해당 주소에 대해 생성자 호출 및 초기화를 진행한다.
    4️
    반환 값은 operator new() 함수의 결과가 아니라, 컴파일러에 의해 형 변환된 객체 주소 값이다.

⚠️ 중요 포인트:

  • 생성자 호출은 operator new() 함수가 아니라 컴파일러의 역할이다.
  • 클래스 멤버 함수 형태로 선언해도, operator new()static 함수처럼 동작하므로 객체 생성과정에서 정상적으로 호출된다.

// new 연산자 오버로딩 예시

void* operator new(size_t size) {

    void* ex = new char[size]; // byte 단위 메모리 할당

    return ex;                 // 주소 반환

}

  • 배열 할당 시에는 다음과 같이 오버로딩할 수 있다.

void* operator new[](size_t size) {

    void* ex = new char[size];

    return ex;

}

 

💻 예제 코드

#include <iostream>

using namespace std;

 

class Ex1 {

    int data;

public:

    Ex1(int d = 0) : data(d) {

        cout << "생성자 호출, data=" << data << endl;

    }

    ~Ex1() {

        cout << "소멸자 호출, data=" << data << endl;

    }

 

    void* operator new(size_t size) {

        cout << "[new 오버로딩] size=" << size << endl;

        return ::operator new(size); // 전역 new 사용

    }

 

    void* operator new[](size_t size) {

        cout << "[new[] 오버로딩] size=" << size << endl;

        return ::operator new[](size);

    }

};

 

int main() {

    Ex1* p = new Ex1(10);

    delete p;

 

    Ex1* arr = new Ex1[3];

    delete[] arr;

}


2. delete 연산자 오버로딩

1) delete 연산자의 기본 개념

  • delete 연산자는 먼저 소멸자를 호출한 후, 메모리 해제를 진행한다.
  • 그 후 오버로딩된 operator delete() 함수가 실행된다.
  • 따라서 delete 오버로딩에서는 메모리 해제를 책임져야한다.

// delete 연산자 오버로딩 예시

void operator delete(void* ex) {

    delete[] (char*)ex; // char* 변환 후 해제

}

배열 소멸 시 호출되는 함수는 다음과 같이 정의한다.

void operator delete[](void* ex) {

    delete[] (char*)ex;

}

⚠️ 주의:

  • delete 연산자 오버로딩 시 void* 그대로 delete 불가하다.
  • 반드시 적절한 형 변환 후 메모리 해제를 진행해야 한다.

 

💻 예제 코드

#include <iostream>

using namespace std;

 

class Ex2 {

    int data;

public:

    Ex2(int d = 0) : data(d) {

        cout << "생성자 호출, data=" << data << endl;

    }

    ~Ex2() {

        cout << "소멸자 호출, data=" << data << endl;

    }

 

    void* operator new(size_t size) {

        cout << "[new 오버로딩] size=" << size << endl;

        return ::operator new(size);

    }

    void operator delete(void* p) {

        cout << "[delete 오버로딩]" << endl;

        ::operator delete(p);

    }

};

 

int main() {

    Ex2* ex = new Ex2(20);

    delete ex;

}


3. 포인터 연산자 오버로딩

1) 포인터 연산자 기본 개념

  • 포인터 연산자: 포인터 기반 연산자로, 대표적으로 ->, * 연산자가 있다.
  • 오버로딩 형태:
    ·
    클래스명* operator->()
    ·
    클래스명& operator*()

(*ex) = 1;          // ex.operator*() = 1;

(*ex).getLen();     // (ex.operator*()).getLen();

ex->getLen();       // ex.operator->()->getLen();

-> 연산자의 반환은 주소 값을 반환해야 하며, 컴파일러가 이를 다시 해석한다.

💻 예제 코드

#include <iostream>

using namespace std;

 

class Ex3 {

    int data;

public:

    Ex3(int d) : data(d) {}

    int getData() { return data; }

 

    Ex3* operator->() { return this; }

    Ex3& operator*() { return *this; }

};

 

int main() {

    Ex3 ex(30);

    Ex3* p = &ex;

 

    cout << "-> 연산자: " << p->getData() << endl;

    cout << "* 연산자: " << (*p).getData() << endl;

}

 

2) 스마트 포인터(Smart Pointer)

  • 스마트 포인터는 포인터처럼 동작하지만, 메모리 관리 자동화 기능을 가진 객체이다.
  • 동적 할당 시 delete를 직접 호출할 필요가 없으며, 소멸자에서 자동으로 메모리 해제를 수행한다.

C++11 이후 표준 스마트 포인터:

  • std::unique_ptr : 단독 소유
  • std::shared_ptr : 참조 횟수 기반 공유
  • std::weak_ptr : shared_ptr의 순환 참조 방지

 

💻 예제 코드

#include <iostream>

#include <memory>

using namespace std;

 

class Ex4 {

public:

    Ex4() { cout << "생성자 호출" << endl; }

    ~Ex4() { cout << "소멸자 호출" << endl; }

    void show() { cout << "메서드 실행" << endl; }

};

 

int main() {

    unique_ptr<Ex4> up(new Ex4());

    up->show();

 

    shared_ptr<Ex4> sp1 = make_shared<Ex4>();

    shared_ptr<Ex4> sp2 = sp1; // 참조 카운트 증가

    sp1->show();

}


4. () 연산자 오버로딩

  • () 연산자도 오버로딩 가능하다.
  • 오버로딩 시 객체를 함수처럼 호출할 수 있다.

ex(1, 2); // == ex.operator()(1, 2);

이 방식으로 함수 객체(펑터)를 구현할 수 있다.

💻 예제 코드

#include <iostream>

using namespace std;

 

class Ex5 {

public:

    int operator()(int a, int b) {

        return a + b;

    }

};

 

int main() {

    Ex5 add;

    cout << "add(3,4) = " << add(3, 4) << endl;

}

 


5. 펑터(Functor)

  • 펑터(Functor)란 함수처럼 동작하는 객체이다.
  • 함수 포인터보다 유연성이 크며, 상태를 저장하거나 추가 동작을 포함할 수 있다.

: STL 알고리즘에서 비교 함수로 자주 사용된다.

struct Compare {

    bool operator()(int a, int b) {

        return a < b;

    }

};

💻 예제 코드

#include <iostream>

#include <algorithm>

#include <vector>

using namespace std;

 

struct Compare {

    bool operator()(int a, int b) {

        return a > b; // 내림차순

    }

};

 

int main() {

    vector<int> v = {5, 2, 8, 1};

    sort(v.begin(), v.end(), Compare());

 

    for (int n : v) cout << n << " ";

}


6. 자동 형 변환

  • C++에서는 객체객체, 객체기본 자료형 간의 대입이 가능하다.
  • 특정 위치에 올 수 없는 데이터가 오면, 생성자를 통해 임시 객체가 생성된다.

:

Ex ex;

ex = 1;        // int -> Ex(int) 생성자 호출 후 대입

ex = Ex(30);   // 임시 객체 생성 후 대입

⚠️ 주의:

  • 이러한 변환은 암묵적으로 발생하므로, 원치 않는 변환을 막고 싶다면 explicit 키워드를 생성자에 사용해야 한다.

 

💻 예제 코드

#include <iostream>

using namespace std;

 

class Ex7 {

    int data;

public:

    Ex7(int d) : data(d) {}

    void show() { cout << "data=" << data << endl; }

};

 

int main() {

    Ex7 ex = 100; // int → Ex7 변환

    ex.show();

}


7. 형 변환 연산자 (Conversion Operator)

  • 형 변환 연산자는 반환형을 명시하지 않는다.
  • 형 변환 연산자를 사용하면 객체를 기본 자료형으로 변환할 수 있다.

operator int() {

    return value;  // 객체를 int로 변환 가능

}

  • 이 방식으로 객체를 기본 자료형으로 변환할 수 있어, 객체와 기본 자료형의 연산도 가능하다.

:

Ex ex1(10);

int n = ex1 + 1; // 객체 → int 변환 후 연산

💻 예제 코드

#include <iostream>

using namespace std;

 

class Ex8 {

    int data;

public:

    Ex8(int d) : data(d) {}

    operator int() { return data; } // int 변환

};

 

int main() {

    Ex8 ex(50);

    int n = ex;

    cout << "int : " << n << endl;

}


🧩 전체 종합 예제

💻 전체 코드

#include <iostream>

#include <memory>

#include <vector>

#include <algorithm>

using namespace std;

 

// 1 & 2. new / delete 오버로딩

class MyClass {

    int data;

public:

    MyClass(int d = 0) : data(d) {

        cout << "생성자 호출, data=" << data << endl;

    }

    ~MyClass() {

        cout << "소멸자 호출, data=" << data << endl;

    }

 

    void* operator new(size_t size) {

        cout << "[new 오버로딩] size=" << size << endl;

        return ::operator new(size);

    }

    void operator delete(void* p) {

        cout << "[delete 오버로딩]" << endl;

        ::operator delete(p);

    }

 

    int getData() { return data; }

    MyClass* operator->() { return this; }   // 포인터 연산자

    MyClass& operator*() { return *this; }   // 포인터 연산자

 

    int operator()(int a, int b) {           // () 연산자

        return a + b + data;

    }

 

    operator int() { return data; }          // 변환 연산자

};

 

// 5. 펑터

struct Compare {

    bool operator()(int a, int b) { return a < b; }

};

 

int main() {

    // new & delete

    MyClass* p = new MyClass(10);

    cout << "데이터 = " << p->getData() << endl;

    delete p;

 

    // 포인터 연산자

    MyClass ex(20);

    cout << "-> 연산자 = " << ex->getData() << endl;

    cout << "* 연산자 = " << (*ex).getData() << endl;

 

    // 스마트 포인터

    unique_ptr<MyClass> up(new MyClass(30));

    up->getData();

 

    // () 연산자

    MyClass addObj(5);

    cout << "addObj(3,4) = " << addObj(3,4) << endl;

 

    // 펑터

    vector<int> v = {3,1,4,2};

    sort(v.begin(), v.end(), Compare());

    cout << "정렬 결과: ";

    for (int n : v) cout << n << " ";

    cout << endl;

 

    // 자동 변환

    MyClass ex2 = 100; // int → MyClass 변환

    cout << "자동 변환 데이터 = " << ex2.getData() << endl;

 

    // 변환 연산자

    int n = ex2; // MyClass → int 변환

    cout << " 변환된 int = " << n << endl;

 

    return 0;

}

실행 결과 (예상)

[new 오버로딩] size=4

생성자 호출, data=10

데이터 = 10

소멸자 호출, data=10

[delete 오버로딩]

생성자 호출, data=20

-> 연산자 = 20

* 연산자 = 20

생성자 호출, data=30

소멸자 호출, data=30

addObj(3,4) = 12

정렬 결과: 1 2 3 4

생성자 호출, data=100

자동 변환 데이터 = 100

변환된 int = 100

소멸자 호출, data=100


마무리 정리

1️⃣ new 연산자 오버로딩은 메모리 할당만 담당하며, 생성자 호출은 컴파일러가 처리한다.
2️⃣ delete
연산자 오버로딩은 소멸자 호출 메모리 해제만 담당한다.
3️⃣
포인터 연산자(->, *) 오버로딩하면 객체를 포인터처럼 사용할 있다.
4️⃣
스마트 포인터는 메모리를 자동 관리한다.
5️⃣ ()
연산자 오버로딩으로 객체를 함수처럼 호출할 있다.
6️⃣
펑터(Functor) 함수처럼 동작하는 객체이다.
7️⃣
자동 변환은 생성자를 통해 임시 객체를 생성한다.
8️⃣
변환 연산자를 사용하면 객체를 기본 자료형으로 변환할 있다.

반응형