[따배씨++]Chapter9. 연산자 오버로딩
카테고리: Cpp
태그: Programming
인프런에 있는 홍정모님의 홍정모의 따라하며 배우는 C++ 강의를 듣고 정리한 내용입니다.
공부하는 식빵맘 님의 블로그를 참고했습니다.
learncpp 교재 내용을 참고했습니다.
🚆 산술 연산자 오버로딩
#include <iostream>
using namespace std;
class A
{
public:
int data_a = 10;
}
int main()
{
A object1;
A object2;
cout << object1 + object2 << endl; // error
return 0;
}
- C++의 경우 연산자의 오버로딩이 가능
- 클래스간의 연산을 정의해서 사용 가능
- add()와 같은 함수를 정의 후 return하는 방식
- 연산자 오버로딩 하는 방식
- 연산자간의 우선순위는 변하지 않음
(return 타입) opeator (연산자) (parameters)
class A
{
public:
int data_a = 10;
};
int operator + (const A &a, const A &b)
{
return a.data_a + b.data_a;
}
int main()
{
A object1;
A object2;
cout << object1 + object2 << endl; // 20
return 0;
}
오버로딩 불가능한 연산자
? true:false
: 3항 연산자::
: namespace범위 지정 연산자sizeof
: 크기 연산자.
,.*
: 멤버 선택 (포인터)연산자
🚆 전역 함수 vs 멤버 함수
전역 함수로 오버로딩
class Cents
{
private:
int m_cents;
public:
Cents(int cents = 0) { m_cents = cents; }
friend int operator+(const Cents &c1, const Cents &c2); // friendly 선언
};
int operator+(const Cents &c1, const Cents &c2)
{
return c1.m_cents + c2.m_cents;
}
int main()
{
Cents cents1(5);
Cents cents2(7);
cout << cents1 + cents2 << endl; // 12
return 0;
}
- private 멤버에 접근해야 하는 경우 getter 함수 이용
- 또는 friendly 함수로 접근
멤버 함수로 오버로딩
class A
{
private:
int data_a = 10;
public:
int operator + (const A &a)
{
return data_a + a.data_a;
}
};
int main()
{
A object1;
A object2;
cout << object1 + object2 << endl; // 20
return 0;
}
- 멤버 함수의 경우 this 파라미터가 생략되어 있음
- priave 변수 접근 가능
🚆 입출력 연산자 오버로딩
입출력 연산에 사용되는 iostream
class는 다음 두 클래스를 상속받는 클래스이다.
- 입력을 담당하는
istream
- 출력을 담당하는
ostream
예를 들어 std::cout은 ostream의 객체.
사실은 모두 오버로딩 해둔 것
ostream& operator<<(bool& val);
ostream& operator<<(short& val);
ostream& operator<<(unsigned short& val);
ostream& operator<<(int& val);
ostream& operator<<(unsigned int& val);
ostream& operator<<(long& val);
ostream& operator<<(unsigned long& val);
ostream& operator<<(float& val);
ostream& operator<<(double& val);
ostream& operator<<(long double& val);
ostream& operator<<(void* val);
...
- 따라서 입출력 연산자를 멤버 함수로 선언하려면 istream/ostream 내부에서 해야 함
- 전역함수로만 정의 가능!
class Point
{
private:
double m_x, m_y, m_z;
public:
Point(double x = 0.0, double y = 0.0, double z = 0.0)
: m_x(x), m_y(y), m_z(z)
{
}
friend std::ostream &operator<<(std::ostream &out, const Point &point)
{
out << "( " << point.m_x << ", " << point.m_y << ", " << point.m_z << " )";
return out;
}
};
int main()
{
Point p1(0.0, 0.1, 0.2);
Point p2(3.4, 1.5, 2.0);
cout << p1 << " " << p2 << endl; // ( 0, 0.1, 0.2 ) ( 3.4, 1.5, 2 )
return 0;
}
- « 연산자의 오버로딩이 클래스 내부에서 정의되어 있어도 전역 함수(friend 특이케이스)
- std::ostream 객체로 return 받아야 chaining 가능
- ostream 객체의 « 오버로딩 하는 방식
🚆 비교 연산자 오버로딩
class Cents
{
private:
int m_cents;
public:
Cents(int cents = 0) { m_cents = cents; }
int getCents() const { return m_cents; }
int &getCents() { return m_cents; }
friend bool operator<(const Cents &c1, const Cents &c2)
{
return c1.m_cents < c2.m_cents;
}
friend bool operator==(const Cents &c1, const Cents &c2)
{
return c1.m_cents == c2.m_cents;
}
friend std::ostream &operator<<(std::ostream &out, const Cents ¢s)
{
out << cents.m_cents;
return out;
}
};
int main()
{
vector<Cents> arr(10);
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
for (unsigned i = 0; i < 10; ++i)
arr[i].getCents() = i;
std::shuffle(begin(arr), end(arr), std::default_random_engine(seed));
for (auto &e : arr)
cout << e << " ";
cout << endl;
std::sort(begin(arr), end(arr));
for (auto &e : arr)
cout << e << " ";
cout << endl;
return 0;
}
- 비교 연산자를 오버로딩 할 경우 sorting 가능
<
,==
연산자를 오버로딩 해야 함- 멤버 함수로 정의하면 정렬이 안됨 (어째서..?)
🚆 증감 연산자 오버로딩
전위 증감 연산자
class Digit
{
private:
int m_digit;
public:
Digit(int digit = 0) : m_digit(digit) {}
friend ostream &operator<<(ostream &out, const Digit &d)
{
out << d.m_digit;
return out;
}
friend Digit &operator++(Digit &digit)
{
++digit.m_digit;
return digit;
}
};
int main()
{
Digit d(5);
cout << ++d << endl; // 6
cout << d << endl; // 6
return 0;
}
- 증감 연산자의 경우 자기 자신을 return godi gka
- Parameter로는 참조 객체를 받아야 하며, 증감 연산이 이루어저야 하므로 const x
멤버 함수로 구현
Digit & operator ++ ()
{
++m_digit;
return *this;
}
후위 증감 연산자
class Digit
{
private:
int m_digit;
public:
Digit(int digit = 0) : m_digit(digit) {}
friend ostream &operator<<(ostream &out, const Digit &d)
{
out << d.m_digit;
return out;
}
// prefix
friend Digit &operator++(Digit &digit)
{
++digit.m_digit;
return digit;
}
// postfix
friend Digit operator++(Digit &digit, int)
{
Digit temp(digit.m_digit);
++digit;
return temp;
}
};
int main()
{
Digit d(5);
cout << d++ << endl; // 5
cout << d << endl; // 6
return 0;
}
- 보통 후위 증감 연산자는 전위 증감 연산자를 통해 구현
- 임시 객체를 만들어 저장하고, 해당 객체를 리턴 받는 방식
- 전위 연산자와 리턴 타입만 다르고 나머지는 동일
- 문제는 리턴 타입이 함수의 시그니쳐가 아니라는 것
- 함수를 구분해주기 위해 dummy parameter (int) 추가
멤버 함수로 구현
Digit operator ++ (int)
{
Digit temp(m_digit);
++(*this)
return temp;
}
🚆 첨자 연산자 오버로딩
멤버 변수로 list를 갖고 있는 경우, 첨자 연산자 오버로딩이 없다면 매우 불편
포인터로 list 꺼낸 후 index로 가져와야 함
class IntList
{
private:
int m_list[10];
public:
int &operator[](const int index)
{
return m_list[index];
}
};
int main()
{
IntList my_list;
my_list[3] = 10;
cout << my_list[3] << endl; // 10
return 0;
}
- 주의) 참조로 리턴해야 L-Value로 사용 가능
class IntList
{
private:
int m_list[10] = {1,2,3,4,5,6,7,8,9,10};
public:
int &operator[](const int index)
{
assert(index >=0);
assert(index<10);
return m_list[index];
}
const int & operator [] (const int index) const
{
assert(index >=0);
assert(index<10);
return m_list[index];
}
};
int main()
{
const IntList my_list;
my_list[3] = 10; // error
cout << my_list[3] << endl; // 10
return 0;
}
- read only일 경우 const용 오버로딩 만들어 줄 수 있음
- (const 여부로도 오버로딩 가능)
- 첨자 연산자의 경우 assert로 범위 지정해주면 좋음
🚆 변환 생성자 - Explicit / Delete
class Fraction
{
private:
int m_numerator;
int m_denominator;
public:
Fraction(int num = 0, int den = 1)
: m_numerator(num), m_denominator(den)
{
assert(den != 0);
}
friend ostream &operator<<(ostream &out, const Fraction &f)
{
out << f.m_numerator << " / " << f.m_denominator << endl;
return out;
}
};
void doSomething(Fraction frac)
{
cout << frac << endl;
}
int main()
{
doSomething(7); // 7/1
return 0;
}
- doSomething(7)의 경우 doSomething(Fraction(7,1))로 인식 : 변환 생성자
컴파일러의 개입 없이 생성자를 명확하게 지정하고 싶은 경우
explicit Fraction(int num = 0, int den = 1)
: m_numerator(num), m_denominator(den)
{
assert(den != 0);
}
int main()
{
doSomething(7) // error
}
- 생성자 앞에 explicit 키워드를 붙여주면 parameters가 정확히 일치해야 함
Fraction(const Fraction &frac) = delete;
- delete : 해당 타입의 parameter는 받지 않겠다
🚆 얕은 복사 vs 깊은 복사
멤버 변수에 포인터(동적 할당 메모리) 객체가 존재하는 경우 복사 생성자 / 대입 연산자 사용에 주의해야 한다.
class MyString
{
public:
char *m_data = nullptr;
int m_length = 0;
MyString(const char *source = "")
{
assert(source);
m_length = strlen(source) + 1;
m_data = new char[m_length];
for (int i=0; i<m_length; ++i)
m_data[i] = source[i];
m_data[m_length - 1] = '\0';
}
// default copy constructor
MyString(const MyString &other)
{
m_data = other.m_data;
m_length = other.m_length;
}
~MyString()
{
delete [] m_data;
}
};
int main()
{
MyString str1("Hello");
MyString str2(str1); // 복사 생성자
MyString str3 = str1; // 복사 생성자
str2 = str1; // 대입 연산자
return 0;
}
- 포인터를 얕은 복사로 가져온 경우 관리가 안됨
- 이런 경우 복사 생성자 / 대입 연산자 오버로딩 해주어야 함
복사 생성자 : 새로 메모리 할당하고 내용 복사
MyString(const MyString &source)
{
m_length = source.m_length;
if (source.m_data != nullptr)
{
m_data = new char[m_length];
for (int i = 0; i < m_length; ++i)
m_data[i] = source.m_data[i];
}
else
m_data = nullptr;
}
대입 연산자 : 복사 생성자와 동일 + 기존 메모리 해제 해주어야 함
MyString& operator = (const MyString & source)
{
if (this == &source) // 자기 자신을 대입하는 경우
return *this;
delete[] m_data; // 자신의 기존 내용물 비워주기
m_length = source.m_length;
if (source.m_data != nullptr)
{
m_data = new char[m_length];
for (int i = 0; i < m_length; ++i)
m_data[i] = source.m_data[i];
}
else
m_data = nullptr;
}
🚆 Initializer_list
커스텀 클래스의 경우 중괄호로 편하게 초기화 하지 못함 -> initialzer_list 이용
#include <iostream>
#include <cassert>
#include <initializer_list>
using namespace std;
class IntArray
{
private:
unsigned m_length = 0;
int *m_data = nullptr;
public:
IntArray(unsigned length)
: m_length(length)
{
m_data = new int[length];
}
IntArray(const std::initializer_list<int> &list)
: IntArray(list.size())
{
int count = 0;
for (auto &element : list)
{
m_data[count] = element;
++count;
}
}
~IntArray()
{
delete[] this->m_data;
}
friend ostream &operator<<(ostream &out, IntArray arr)
{
for (unsigned i = 0; i < arr.m_length; ++i)
out << arr.m_data[i] << " ";
out << endl;
return out;
}
};
int main()
{
IntArray int_array = {1, 2, 3, 4, 5}; // 중괄호로 생성자 호출 가능
cout << int_array << endl;
return 0;
}
- #include
- initialize_list의 경우 []접근 못함
- 개별 원소를 얕은 복사로 가져오기 때문에 필요한 경우 깊은 복사로 오버로딩 해야함
댓글 남기기