** 연산자 오버로딩
: 기존에 존재하던 연산자의 기본 기능 외에 다른 기능을 추가할 수 있음
ㄴ 연산자를 오버로딩 한 함수도 const 선언 가능
** 연산자를 오버로딩 하는 두가지 방법
1) 멤버함수에 의한 연산자 오버로딩
#include <iostream>
using namespace std;
class Point
{
private:
int xPos, yPos;
public:
Point(int x = 0, int y = 0) : xPos(x), yPos(y)
{ }
void ShowPosition() const
{
cout << '[' << xPos << ", " << yPos << ']' << endl;
}
Point operator+(const Point& ref) // 멤버함수에 의한 연산자 오버로딩
{
Point pos(xPos + ref.xPos, yPos + ref.yPos);
return pos;
}
};
int main(void)
{
Point pos1(3, 4);
Point pos2(10, 20);
Point pos3 = pos1 + pos2;
Point pos4 = pos1.operator+(pos2);
pos1.ShowPosition();
pos2.ShowPosition();
pos3.ShowPosition();
pos4.ShowPosition();
return 0;
}
2) 전역함수에 의한 연산자 오버로딩
#include <iostream>
using namespace std;
class Point
{
private:
int xPos, yPos;
public:
Point(int x = 0, int y = 0) : xPos(x), yPos(y)
{ }
void ShowPosition() const
{
cout << '[' << xPos << ", " << yPos << ']' << endl;
}
friend Point operator+(const Point& pos1, const Point& pos2); // 전역함수에 의한 연산자 오버로딩
};
Point operator+(const Point& pos1, const Point& pos2) // 전역함수에 의한 연산자 오버로딩
{
Point pos(pos1.xPos + pos2.xPos, pos1.yPos + pos2.yPos);
return pos;
}
int main(void)
{
Point pos1(3, 4);
Point pos2(10, 20);
Point pos3 = pos1 + pos2;
Point pos4 = operator+(pos1, pos2);
pos1.ShowPosition();
pos2.ShowPosition();
pos3.ShowPosition();
pos4.ShowPosition();
return 0;
}
** 객체지향에는 '전역(global)'에 대한 개념이 존재 하지 않음
C++은 C 스타일의 코드구현이 가능한 언어이기 때문에 전역에 대한 개념이 존재
따라서, 특별한 경우가 아니라면 멤버함수를 기반으로 연산자를 오버로딩하는 것이 좋음
** 멤버함수 기반 기준 - 오버로딩이 불가능한 연산자
. | 멤버 접근 연산자 |
.* | 멤버 포인터 연산자 |
:: | 범위 지정 연산자 |
조건?참:거짓 | 조건 연산자(3항 연산자) |
sizeof | 바이트 단위 크기 계산 |
typeid | RTTI 관련 연산자 |
static_cast | 형변환 연산자 |
dynamic_cast | 형변환 연산자 |
const_cast | 형변환 연산자 |
reinterpret_cast | 형변환 연산자 |
** 멤버함수 기반 기준 - 오버로딩이 가능한 연산자
= | 대입연산자 |
() | 함수 호출 연산자 |
[] | 배열 접근 연산자 |
-> | 멤버 접근을 위한 포인터 연산자 |
** 연산자를 오버로딩 주의사항
1) 본래의 의도를 벗어난 형태의 연산자 오버로딩은 좋지 않다
2) 연산자의 우선순위와 결합성은 바뀌지 않는다
3) 매개변수의 디폴터 값 설정이 불가능하다
4) 연산자의 순수 기능까지 빼앗을 수는 없다
** 단항 연산자 - 증가 연산자(++)와 감소 연산자(--)의 오버로딩
1) 전위
ㄴ ++pos → pos.operator++();
ㄴ --pos → pos.operator--();
#include <iostream>
using namespace std;
class Point
{
private:
int xPos, yPos;
public:
Point(int x = 0, int y = 0) : xPos(x), yPos(y)
{ }
void ShowPosition() const
{
cout << '[' << xPos << ", " << yPos << ']' << endl;
}
Point& operator++()
{
xPos += 1;
yPos += 1;
return *this;
}
friend Point& operator--(Point& ref);
};
Point& operator--(Point& ref)
{
ref.xPos -= 1;
ref.yPos -= 1;
return ref;
}
int main(void)
{
cout << "start" << endl;
Point pos(1, 1);
pos.ShowPosition();
cout << "===============" << endl;
++pos;
pos.ShowPosition();
--pos;
pos.ShowPosition();
cout << "===============" << endl;
pos.operator++();
pos.ShowPosition();
operator--(pos);
pos.ShowPosition();
cout << "===============" << endl;
++(++pos);
pos.ShowPosition();
--(--pos);
pos.ShowPosition();
return 0;
}
2) 후위
ㄴ pos++ → pos.operator++(int);
ㄴ pos-- → pos.operator--(int)
#include <iostream>
using namespace std;
class Point
{
private:
int xPos, yPos;
public:
Point(int x = 0, int y = 0) : xPos(x), yPos(y)
{ }
void ShowPosition() const
{
cout << '[' << xPos << ", " << yPos << ']' << endl;
}
const Point operator++(int)
{
const Point result(xPos, yPos);
xPos += 1;
yPos += 1;
return result;
}
friend const Point operator--(Point& ref, int);
};
const Point operator--(Point& ref, int)
{
const Point result(ref);
ref.xPos -= 1;
ref.yPos -= 1;
return result;
}
int main(void)
{
cout << "start" << endl;
Point pos(1, 1);
pos.ShowPosition();
cout << "===============" << endl;
Point cpy;
cpy = pos++;
cpy.ShowPosition();
pos.ShowPosition();
cout << "===============" << endl;
cpy = pos--;
cpy.ShowPosition();
pos.ShowPosition();
return 0;
}
** 자료형이 다른 두 피연산자를 대상으로 하는 연산
// pos * 3
Point operator*(int num)
{
Point pos = Point(xPos * num, yPos * num);
return pos;
}
// 3 * pos
Point operator*(int num, Point& ref)
{
Point pos = Point(ref.xPos * num, ref.yPos * num);
return pos;
}
** 대입 연산자의 오버로딩
- 정의하지 않으면 디폴트 복사 생성자가 삽입
- 디폴트 복사 생성자는 멤버 대 멤버의 복사(얕은 복사)함
- 생성자 내에서 동적 할당을 한다면, 그리고 깊은 복사가 필요하다면 직접 정의 필요
#include <iostream>
using namespace std;
class First
{
public:
int num1, num2;
char ch1;
char* word1;
First(int n1 = 0, int n2 = 0, char c1 = 'A', const char* s = "Default") : num1(n1), num2(n2), ch1(c1)
{
word1 = new char[strlen(s) + 1];
strcpy_s(word1, strlen(s) + 1, s);
}
void ShowData()
{
cout << num1 << ", " << num2 << ", " << ch1 << ", " << word1 << endl;
}
};
class Second
{
public:
int num3, num4;
char ch3;
char* word3;
Second(int n3 = 0, int n4 = 0, char c3 = 'A', const char* s = "Default") : num3(n3), num4(n4), ch3(c3)
{
word3 = new char[strlen(s) + 1];
strcpy_s(word3, strlen(s) + 1, s);
}
void ShowData()
{
cout << num3 << ", " << num4 << ", " << ch3 << ", " << word3 << endl;
}
Second& operator=(const Second& ref)
{
cout << "Second& operator=()" << endl;
num3 = ref.num3;
num4 = ref.num4;
ch3 = ref.ch3;
word3 = new char[strlen(ref.word3) + 1];
strcpy_s(word3, strlen(ref.word3) + 1, ref.word3);
return *this;
}
};
int main(void)
{
First fsrc(111, 222, 'A', "nice to meet you - 1");
First fcpy = fsrc;
Second ssrc(333, 444, 'B', "nice to meet you - 1");
Second scpy = ssrc;
fsrc.ShowData();
fcpy.ShowData();
cout << endl;
ssrc.ShowData();
scpy.ShowData();
cout << endl << "============================" << endl << endl;
////////////////////////////////////////////////////////////
const char* changeWord = "nice to meet you - 2";
strcpy_s(fsrc.word1, strlen(changeWord) + 1, changeWord);
fsrc.ch1 = 'Z';
strcpy_s(ssrc.word3, strlen(changeWord) + 1, changeWord);
ssrc.ch3 = 'Y';
////////////////////////////////////////////////////////////
fsrc.ShowData();
fcpy.ShowData();
cout << endl;
ssrc.ShowData();
scpy.ShowData();
cout << endl << "============================" << endl << endl;
////////////////////////////////////////////////////////////
First fob1, fob2;
Second sob1, sob2;
fob1 = fob2 = fsrc;
sob1 = sob2 = ssrc;
cout << endl << "============================" << endl << endl;
fob1.ShowData();
fob2.ShowData();
cout << endl;
sob1.ShowData();
sob2.ShowData();
cout << endl << "============================" << endl << endl;
////////////////////////////////////////////////////////////
const char* changeWord2 = "nice to meet you - 3";
strcpy_s(fob1.word1, strlen(changeWord2) + 1, changeWord2);
strcpy_s(sob1.word3, strlen(changeWord2) + 1, changeWord2);
fsrc.ShowData();
fcpy.ShowData();
fob1.ShowData();
fob2.ShowData();
cout << endl;
ssrc.ShowData();
scpy.ShowData();
sob1.ShowData();
sob2.ShowData();
return 0;
}
** 얕은 복사로 인해서, 객체의 소멸과정에서 지워진 문자열을 중복 소멸하는 문제가 발생할 수 있음
따라서 깊은 복사로 정의하고
메모리 누수가 발생하지 않도록, 깊은 복사에 앞서 메모리 해제의 과정을 거친다
** 상속 구조에서의 대입 연산자 호출
: 유도 클래스의 대입 연산자 정의에서 명시적으로 기초 클래스의 대입 연산자 호출문을 삽입하지 않으면
기초 클래스의 대입연산자는 호출되지 않아서,
기초 클래스의 멤버변수는 멤버 대 멤버의 복사 대상에서 제외
#include <iostream>
using namespace std;
class First
{
private:
int num1, num2;
public:
First(int n1 = 0, int n2 = 0) : num1(n1), num2(n2)
{}
void ShowData()
{
cout << num1 << ", " << num2 << endl;
}
First& operator=(const First& ref)
{
cout << "Second& operator=()" << endl;
num1 = ref.num1;
num2 = ref.num2;
return *this;
}
};
class Second : public First
{
private:
int num3, num4;
public:
Second(int n1 = 0, int n2 = 0, int n3 = 0, int n4 = 0)
: First(n1, n2), num3(n3), num4(n4)
{}
void ShowData()
{
First::ShowData();
cout << num3 << ", " << num4 << endl;
}
Second& operator=(const Second& ref)
{
cout << "Second& operator=()" << endl;
First::operator=(ref); // 기초 클래스의 대입 연산자 호출
num3 = ref.num3;
num4 = ref.num4;
return *this;
}
};
int main(void)
{
Second ssrc(111, 222, 333, 444);
Second scpy(0, 0, 0, 0);
scpy = ssrc;
scpy.ShowData();
return 0;
}
** 이니셜라이저가 성능 향상에 도움주는 이유?
: 이니셜라이저를 이용하면 선언과 동시에 초기화가 이뤄지는 형태로 바이너리 코드가 생성
즉, 함수의 호출 횟수를 줄일 수 있고 초기화 과정을 단순화 시킬수 있어서 약간의 성능 향상을 기대할 수 있음
** 배열요소에 접근할 때 사용하는 [] 연산자
ㄴ [문제점] C와 C++의 기본 배열은 경계 검사를 하지 않음
ㄴ [해결책] [] 연산자 오버로딩을 통해 잘못된 배열 접근을 막을 수 있음
#include <iostream>
#include <cstdlib>
using namespace std;
class BoundCheckIntArray
{
private:
int* arr;
int arrlen;
public:
BoundCheckIntArray(int len) : arrlen(len)
{
arr = new int[len];
}
int& operator[] (int idx)
{
if (idx < 0 || idx >= arrlen)
{
cout << "Array index out of bound exception" << endl;
exit(1);
}
return arr[idx];
}
~BoundCheckIntArray()
{
delete[] arr;
}
};
int main(void)
{
BoundCheckIntArray arr(5);
for (int i = 0; i < 5; i++)
arr[i] = (i + 1) * 11;
for (int i = 0; i < 6; i++)
cout << arr[i] << endl;
return 0;
}
#include <iostream>
#include <cstdlib>
using namespace std;
class BoundCheckIntArray
{
private:
int* arr;
int arrlen;
BoundCheckIntArray(const BoundCheckIntArray& arr) {}
BoundCheckIntArray& operator=(const BoundCheckIntArray& arr) {}
public:
BoundCheckIntArray(int len) : arrlen(len)
{
arr = new int[len];
}
int& operator[] (int idx)
{
if (idx < 0 || idx >= arrlen)
{
cout << "Array index out of bound exception" << endl;
exit(1);
}
return arr[idx];
}
// ShowAllData에서의 ref[len] 인덱스 연산은 컴파일 에러
// 왜? 매개변수가 const BoundCheckIntArray& ref 이기 때문에
// 따라서 인덱스 오버로딩 const 추가
int& operator[] (int idx) const
{
if (idx < 0 || idx >= arrlen)
{
cout << "Array index out of bound exception" << endl;
exit(1);
}
return arr[idx];
}
int GetArrLen() const
{
return arrlen;
}
~BoundCheckIntArray()
{
delete[] arr;
}
};
void ShowAllData(const BoundCheckIntArray& ref)
{
int len = ref.GetArrLen();
for (int idx = 0; idx < len; idx++)
{
cout << ref[len] << endl;
}
}
int main(void)
{
BoundCheckIntArray arr(5);
for (int i = 0; i < 5; i++)
arr[i] = (i + 1) * 11;
for (int i = 0; i < 6; i++)
cout << arr[i] << endl;
return 0;
}
** 객체 저장을 위한 배열 클래스 정의
: 아래의 예제와 같이 주소값을 저장하는 경우,
객체의 생성과 소멸을 위한 new, delete 연산 때문에 신경 쓸 것이 많아 보이지만
깊은 복사인지 얕은 복사인지 하는 문제를 신경쓰지 않아도 되기 때문에 이 방법을 많이 사용한다고 함
#include <iostream>
#include <cstdlib>
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);
};
ostream& operator<<(ostream& os, const Point pos)
{
os << '[' << pos.xpos << "," << pos.ypos << ']' << endl;
return os;
}
typedef Point* POINT_PTR;
class BoundCheckPointPtrArray
{
private:
POINT_PTR* arr;
int arrlen;
BoundCheckPointPtrArray(const BoundCheckPointPtrArray& arr) {}
BoundCheckPointPtrArray& operator=(const BoundCheckPointPtrArray& arr) {}
public:
BoundCheckPointPtrArray(int len) : arrlen(len) { arr = new POINT_PTR[len]; }
POINT_PTR& operator[] (int idx)
{
if (idx < 0 || idx >= arrlen)
{
cout << "Array index out of bound exception" << endl;
exit(1);
}
return arr[idx];
}
POINT_PTR& operator[] (int idx) const
{
if (idx < 0 || idx >= arrlen)
{
cout << "Array index out of bound exception" << endl;
exit(1);
}
return arr[idx];
}
int GetArrLen() const { return arrlen; }
~BoundCheckPointPtrArray() { delete[] arr; }
};
int main(void)
{
BoundCheckPointPtrArray arr(3);
arr[0] = new Point(3, 4);
arr[1] = new Point(5, 6);
arr[2] = new Point(7, 8);
for (int i = 0; i < arr.GetArrLen(); i++)
{
cout << *(arr[i]);
}
delete arr[0];
delete arr[1];
delete arr[2];
return 0;
}
** new와 delete도 연산자이기 때문에, 오버로딩 가능
=> new 연산자의 역할
① 메모리 공간의 할당
② 생성자의 호출
③ 할당하고자 하는 자료형에 맞게 반환된 주소 값을 형 변환
=> 1번에 해당하는 메모리 공간의 할당만 오버로딩 가능
=> 반환형은 반드시 void 포인터 형이어야 하고,
매개변수형은 size_t이어야 함
이렇게 오버로딩 된 함수는 컴파일러에 의해서 호출
=> size_t
typedef unsigned int size_t;
→ 0 이상의 값을 표현할 목적으로 정의된 자료형
https://nomad-programmer.tistory.com/362
#include <iostream>
#include <cstdlib>
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)
{
cout << "opterator new : " << size << endl;
void* adr = new char[size];
return adr;
}
void operator delete (void* adr)
{
cout << "opterator delete ()" << endl;
delete[] adr;
}
};
ostream& operator<<(ostream& os, const Point pos)
{
os << '[' << pos.xpos << "," << pos.ypos << ']';
return os;
}
int main(void)
{
Point* ptr = new Point(3, 4);
cout << *ptr << endl;
delete ptr;
return 0;
}
** 스마트 포인터(Smart Pointer)
스마트 포인터는 객체
즉, 포인터의 역할을 하는 객체라는 의미임
#include <iostream>
#include <cstdlib>
using namespace std;
class Point
{
private:
int xpos, ypos;
public:
Point(int x = 0, int y = 0) : xpos(x), ypos(y)
{
cout << "Point 객체 생성" << endl;
}
~Point()
{
cout << "Point 객체 소멸" << endl;
}
void SetPos(int x, int y)
{
xpos = x;
ypos = y;
}
friend ostream& operator<<(ostream& os, const Point& pos);
};
ostream& operator<<(ostream& os, const Point& pos)
{
os << '[' << pos.xpos << "," << pos.ypos << ']' << endl;
return os;
}
class SmartPtr
{
private:
Point* posptr;
public:
SmartPtr(Point* ptr) : posptr(ptr)
{}
// 스마트 포인터의 기본 함수 - 1
Point& operator*() const
{
return *posptr;
}
// 스마트 포인터의 기본 함수 - 2
Point* operator->() const
{
return posptr;
}
~SmartPtr()
{
delete posptr;
}
};
int main(void)
{
SmartPtr sptr1(new Point(1, 2));
SmartPtr sptr2(new Point(3, 4));
SmartPtr sptr3(new Point(5, 6));
cout << endl;
cout << *sptr1;
cout << *sptr2;
cout << *sptr3;
cout << endl;
sptr1->SetPos(10, 20);
sptr2->SetPos(30, 40);
sptr3->SetPos(50, 60);
cout << *sptr1;
cout << *sptr2;
cout << *sptr3;
cout << endl;
return 0;
}
** 펑터(Functor)
: 객체를 함수처럼 사용 가능, 함수 또는 객체의 동작방식에 유연함을 제공할 때 주로 사용
ㄴ () 연산자 : 함수에 호출에 사용되는, 인자의 전달에 사용, 오버로딩 가능
#include <iostream>
using namespace std;
class Point
{
private:
int xpos, ypos;
public:
Point(int x = 0, int y = 0) : xpos(x), ypos(y)
{ }
Point operator+(const Point& pos) const
{
return Point(xpos + pos.xpos, ypos + pos.ypos);
}
friend ostream& operator<<(ostream& os, const Point& pos);
};
ostream& operator<<(ostream& os, const Point& pos)
{
os << '[' << pos.xpos << "," << pos.ypos << ']' << endl;
return os;
}
// 펑터(Functor) == 함수 오브젝트(Function Object)
class Adder
{
public:
int operator()(const int& n1, const int& n2)
{
return n1 + n2;
}
double operator()(const double& e1, const double& e2)
{
return e1 + e2;
}
Point operator()(const Point& pos1, const Point& pos2)
{
return pos1 + pos2;
}
};
int main(void)
{
Adder adder;
cout << adder(1, 3) << endl;
cout << adder(1.5, 3.7) << endl;
cout << adder(Point(3, 4), Point(7, 9));
return 0;
}
#include <iostream>
using namespace std;
class SortRule
{
public:
virtual bool operator()(int num1, int num2) const = 0;
};
class AscendingSort : public SortRule // 오름차순
{
public:
bool operator()(int num1, int num2) const
{
if (num1 > num2)
return true;
else
return false;
}
};
class DescendingSort : public SortRule // 내림차순
{
bool operator()(int num1, int num2) const
{
if (num1 < num2)
return true;
else
return false;
}
};
class DataStorage // int형 정수의 저장소
{
private:
int* arr;
int idx;
const int MAX_LEN;
public:
DataStorage(int arrlen) : idx(0), MAX_LEN(arrlen)
{
arr = new int[MAX_LEN];
}
void AddData(int num)
{
if (MAX_LEN <= idx)
{
cout << "더 이상 저장이 불가능합니다." << endl;
return;
}
arr[idx++] = num;
}
void ShowAllData()
{
for (int i = 0; i < idx; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
void SrotData(const SortRule& functor)
{
for (int i = 0; i < idx - 1; i++)
{
for (int j = 0; j < idx - 1 - i; j++)
{
if (functor(arr[j], arr[j + 1]))
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
};
int main(void)
{
DataStorage storage(5);
storage.AddData(40);
storage.AddData(30);
storage.AddData(50);
storage.AddData(20);
storage.AddData(10);
storage.SrotData(AscendingSort());
storage.ShowAllData();
storage.SrotData(DescendingSort());
storage.ShowAllData();
storage.AddData(100);
return 0;
}
** 임시객체로의 자동 형 변환과 형 변환 연산자(Conversion Operator) => 일치하지 않는 자료형 간의 대입 연산
1) 기본 자료형 데이터를 객체로 형 변환하는 것은 적절한 생성자의 정의를 통해서 가능
(예) num = 30;
num = Number(30); // 1단계. 임시객체 생성
num.operator=(Number(30)); // 2단계. 임시객체를 대상으로 하는 대입 연산자 호출
=> A형 객체가 와야 할 위치에 B형 데이터(또는 객체)가 왔을 경우,
B형 데이터를 인자로 받는 A형 클래스의 생성자 호출을 통해서 A형 임시객체를 생성함
2) 객체를 기본 자료형 데이터로 형 변환 가능
(예) Number num2 = num1 + 20; 의 연산을 하기 위한 형 변환 연산자
operator int ()
{
return num;
}
▼ 1)과 2)가 반영된 소스 코드
#include <iostream>
using namespace std;
class Number
{
private:
int num;
public:
Number(int n = 0) : num(n)
{
cout << "Number(int n = " << num << ")" << endl;
}
Number& operator=(const Number& ref)
{
cout << "operator=()" << endl;
num = ref.num;
return *this;
}
operator int() // 형 변환 연산자의 오버로딩
{
return num;
}
void ShowNumber() { cout << num << endl; }
};
int main(void)
{
Number num1;
num1 = 30; // 일치하지 않는 자료형 간의 대입연산
Number num2 = num1 + 20;
num2.ShowNumber();
return 0;
}
** 해당 글은 윤성우의 열혈 C++ 프로그래밍 도서를 읽고 정리한 글입니다.
'IT공부 > IT서적' 카테고리의 다른 글
[뇌를 자극하는 윈도우즈 시스템 프로그래밍] 1장. 컴퓨터 구조 - 1 (0) | 2024.12.20 |
---|---|
[윤성우 열혈 C++프로그래밍] Part4. 객체지향의 완성 - 2 (0) | 2024.09.24 |
[윤성우 열혈 C++프로그래밍] Part3. 객체지향의 전개 (0) | 2024.09.14 |
[윤성우 열혈 C++프로그래밍] Part2. 객체지향의 도입 (0) | 2024.08.15 |
[윤성우 열혈 C++프로그래밍] Part1. c++로의 전환 (0) | 2024.08.08 |