이번 강좌에서는
멤버 함수가 아닌 연산자 함수 오버로딩
입출력 연산자 오버로딩 (정확히 보면 <<, >> 연산자)
첨자 연산자 [] 오버로딩
타입 변환 연산자 오버로딩
증감 연산자 ++, -- 오버로딩
friend 키워드는 클래스 내부에서 다른 클래스나 함수들을 friend로 정의할수 있는데,
friend로 정의된 클래스나 함수들은 원래 클래스의 private 로 정의된 변수나 함수들에 접근할수 있다.
class A
{
private:
void private_func() {}
int private_num;
// B 는 A 의 친구!
friend class B;
// func 은 A 의 친구!
friend void func();
};
class B
{
public:
void b()
{
A a;
// 비록 private 함수의 필드들이지만 친구이기
// 때문에 접근 가능
a.private_func();
a.private_num = 2;
}
};
void func()
{
A a;
// 마찬가지로 프라이빗이지만 접근 가능.
a.private_func();
a.private_num = 2;
}
int main() {}
// B 는 A 의 친구!
friend class B;
// func 은 A 의 친구!
friend void func();
위 처럼 클래스 B와 ,void함수 func 을 A 의 친구라 선언했다.
이렇게 친구라 서언 하면
B와 func 안에서 A 의 모든 private 멤버 함수들과 멤버 변수들에 대한 접근 권한을 부여하게 된다.
그러나
B는 A의 모든 private들을 볼 수있지만, B 안에서 A를 friend 로 지정 하지 않는 이상 , A 는 B의 private개체들에 접근 불가능
예를들어
class A
{
private:
int x;
friend class B;
};
class B
{
private:
int y;
};
B에는 A가 friend 라고 지정 하지 않았으므로,
A 는 B의 private 변수인 int y 에 접근 불가능.
a = a + "-1.1";
은 되지만
a = "-1.1" + a
Q.는 안 됨 그 이유는??
A.임의의 연산자 @ 라 칭함. (실제론 없음 C++에서)
*a.operator@(b);
*operator@(a,b);
컴파일러는 둘 중 가능한 녀석을 택해서 처리한다.
a.operator@(b) 에서의 operator@ 는 a의 클래스의 멤버함수로써 사용되는것이고,
operator@(a,b) 에서의 operator@ 는 클래스 외부에서 정의되어 있는 일반적인 함수를 의미하게 된다.
따라서 이경우를 처리하기 위해 함수를 정의해봅시다.
참고로 이는 일부 연산자들에 대해서는 해당되지 않는데 대표적으로 [] 연산자 (첨자), -> 연산자 (멤버 접근), 대입 연산자 (=), () 함수 호출 연산자들의 경우 멤버 함수로만 존재할 수 있습니다. 즉, 따로 멤버 함수가 아닌 전역 함수로 뺄 수 없다는 의미 입니다. 따라서 이들 함수를 오버로딩 할 때 주의하시기 바랍니다.
해결 방법은 일단 스킵
std::cout << a;
이건 사실 std::cout.operator<<(a)
를 하는것.
즉 어떤 std::cout
이라는 객체에 멤버함수 operator<<
각 정의 되어 있어서 a
를 호출하게 되는것.
그런데 std::cout 이 int나 double 변수 심지어 문자열 까지 자유자재로 출력 가능한 이유는
그 경우모두 오버라이딩 되어 있기 때문이다 .operator<<
실제로 우리가 include 하던 iostream의 헤더 파일을 살펴보면 (실제로는 ostream에있음, iostream이 ostream을 include 하고있음)
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);
그렇다면 우리의 Complex 클래스에서 ostream 클래스의 연산자 operator<< 를 자유롭게 사용할수 있으면 어떨까??
ex)
Complex c;
std::cout << c;
은 마치
Complex c;
c.println();
ostream 클래스에 operator<< 멤버 함수를 새롭게 추가하는것은 불가능 합니다.
이를 위해선 표준 헤더파일의 내용을 수정해야 하기 때문이다.
따라서 우리는 ostream클래스에 Complex 객체를 오버로딩 하는 operator<< 연산자 함수를 추가할 수는 없다.
하지만 우리는 클래스의 연산자 함수를 추가하는 방법으로 ,
멤버함수를 사용하는 것 말고도 한 가지더 있다.
바로 ostream클래스 객체와 Complex 객체 두개를 인자로 받는 전역 operator<< 함수를 정의하면 됩니다.
std::ostream& operayot<<(std::ostream& os, const Complex& c)
{
os << "( " << c._real << " , " << c._img << " )";
return os;
}
참고로 operator<<
에서 ostream&
타입을 리턴하는 이뉴는 다음과 같은 문장을 처리할수 있기 위함이다.
std::cout << "a value is : " << a << ". " << std::endl;
하지만 위 operator<<
의 경우 한가지 문제가 있는데
operator<<
에선 c._real 과 c._img 에 접근할수 없다
왜냐하면 _real, _img 모두 Complex 클래스의 private 멤버 변수 이기 때문.
따라서 이를 해결하기 위해 세가지 방법을 고려할수있다.
그냥 _real 과 _img 를 public으로 바꾼다.
Complex 에 print(std::ostream& os)
와 같은 멤버함수를 추가한 뒤, 이를 operator<<
에서 호출한다.
위 operator<< 를 friend로 지정한다.
3번째로 해보자.
friend ostream& operator<<(ostream& os, const Complex& c);
위와 같이 friend 선언을 해주면 된다.
물론 무분별하게 friend 키워드 남발하는것 권장 하지 않는다.
비슷한 방법으로 Complex 객체 c 에 대해 cin >> c;
와 같은 작업을 할 수 있다.
다만 cin 은 istream 객체이고, operator>> 를 오버로딩 해야된다.
#include <iostream>
#include <cstring>
class Complex {
private:
double real, img;
double get_number(const char* str, int from, int to) const;
public:
Complex(double real, double img) : real(real), img(img) {}
Complex(const Complex& c) { real = c.real, img = c.img; }
Complex(const char* str);
Complex& operator+=(const Complex& c);
Complex& operator=(const Complex& c);
// 나머지 연산자 함수들은 생략 :)
friend std::ostream& operator<<(std::ostream& os, const Complex& c);
friend Complex operator+(const Complex& a, const Complex& b);
};
std::ostream& operator<<(std::ostream& os, const Complex& c) {
os << "( " << c.real << " , " << c.img << " ) ";
return os;
}
+ ...
pass
Wrapper 클래스는 무언가를 포장하는 클래스.
C++에서 어떤 경우에 기본 자료형 들을 객체로써 다루어야 할 때가 있습니다.
이럴때 기본 자료형들 (int, float 등등) 을 클래스로 포장해서 각각의 자료형을 객체로 사용하는것을 Wrapper클래스를 이용한다는뜻.
즉 int 자료형을 감싸는 int Wrapper 클래스 Int 는 다음과 같이 구상할수있다.
~pass
++,--
++a;, a++;
둘다 operator++ 이기 때문에
operaotr++();
operator--();
은 전위 증감 연산자 (++x, --x)를 오버로딩 하게된다.
operator++(int x);
operator--(int x);
는 후위 증감 연산자.
물론 인자 x는 아무런 의미가 없다.
단순히 컴파일러 상에서 전위와 후위를 구분하기 위해 int 인자를 넣은것.
실제로 인자로 들어가는 값을 사용할 경우는 없다.
따라서 그냥
operator++(int);
operator--(int);
로 해도 무방.
왜냐면
int x =1;
func(++x);
>> func 에는 2가 인자로 전달됨.
int x =1;
func(x++);
>> func에1이 인자로 전달 되고 나중에 x++ 되어서 2가됨,
A& operator++()
{
//A++ 을 수행한다.
return *this;
}
++ 연산 수행후에 자기자신을 반환.
A operator++(int)
{
A temp(A);
// A++ 수행
return temp;
}
따라서 후위 증감 연산의 경우 추가적으로 복사 생성자를 호출하기 때문에 전위 증감 연산보다 더 느립니다
두개의 동등한 객체 사이에서의 이항 연산자는 멤버 함수가 아닌 외부 함수로 오버로딩 하는것이 좋다
(예를들어 Complex 의 operator+(const Complex&, const Complex& const)
와 같이 말이다.)
두 개의 객체 사이의 이항 연산자 이지만 한 객체만 값이 바뀐다던지
동등하지 않는 이항 연산자는 멤버 함수로 오버로딩 하는것이 좋습니다.
( ex. operator+=
는 이항 연산자 이지만 operator+=(const Complex&)
가 낫다)
단항 연산자는 멤버 함수로 오버로딩 하는것이 좋다.
( ex. operator++ 의 경우 멤버 함수로 오버로딩 합니다)
일부 연산자들은 반드시 멤버 함수로만 오버로딩 해야한다.