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️⃣ 형 변환 연산자를 사용하면 객체를 기본 자료형으로 변환할 수 있다.
'Language > C++' 카테고리의 다른 글
| 📌 Chapter 12 - 템플릿 (0) | 2025.10.24 |
|---|---|
| 📌 Chapter 11 - string (0) | 2025.10.11 |
| 📌 Chapter 10 - 첨자 연산자 오버로딩 (Subscript Operator) (0) | 2025.09.09 |
| 📌 Chapter 10 - 기본 대입 연산자 오버로딩 (0) | 2025.09.08 |
| 📌 Chapter 10 – 연산자 오버로딩 (0) | 2025.09.03 |