오늘의 TIL 목차 (22. 09. 08)
- 연산자 오버로딩(feat. 단항 연산자 오버로딩)
- 함수 객체
- inline
연산자 오버로딩
[ 연산자 오버로딩 ]
: 사용자 정의 타입(구조체, 클래스)의 자료형 간의 연산을 수행하기 위해 연산자의 이름을 이용한 함수를 만들어 주는 문법
class 클래스명
{
// 연산자 종류 : +, -, *, /, ==, ++, --, etc.
반환타입 operator+(매개변수) { 몸체코드; }
}
void main(void)
{
클래스명 C;
C + 매개변수; // C.operator+(매개변수);와 동일
}
-
class CDainn()
{
public:
int m_iA, m_iB;
public:
CDainn(int _iA, int _iB) : m_iA(0), m_iB(0); // 기본 생성자
CDainn(int _iA, int _iB) : m_iA(_iA), m_iB(_iB); // 매개변수 생성자
CDainn operator+(CDainn _class); // + 덧셈 연산 오버로딩 ( + 기능을 헤치면 안됨 )
}
CDainn& CDainn::operator+(CDainn _class){
m_iA += _class.m_iA;
m_iB += _class.m_iB;
return *this;
}
void main(void)
{
CDainn cd(10, 20);
CDainn cdainn(1, 2);
cd + cdainn; // cd.operator+(cdainn); 과 동일
cout << cd.m_iA << ", " << cd.m_iB << endl; // public이라 접근 가능한거임 원래 안됨
}
출력 결과:
11, 22
■ 기능 / 주의
- 연산자 오버로딩은 클래스 내에서 메소드로서만 사용 가능하다. (선언부, 정의부 따로 선언 가능)
- 대입 연산자 ( = )의 경우 디폴트 연산자 (단순 1대1 대입)가 있다. ( 다른 연산자는 디폴트 존재 X )
- 대입 연산자의 디폴트 연산자는 단순 1대1 대입을 하기 때문에 구조체나 포인터가 있을 경우 얕은 복사가 이뤄진다.
- 깊은 복사를 위한 대입 연산자를 명시적으로 선언해줘야 한다. ( 복사 생성자와 동일 ) - 연산자 오버로딩은 매개변수를 하나만 받을 수 있다. (따라서 대부분 자기 자신과, 들어온 매개변수를 연산)
- 매개변수 타입이 다르다면 함수 오버로딩 가능 ( ex. 객체와 int형 operator+ 도 가능 ) - 연산자 오버로딩의 종류는 +, -, *, /, ==, ++, -- 등이 있다.
- 연산자 오버로딩 시 복사 생성을 최소화하기 위해 레퍼런스를 주로 사용한다. ( 원본 참조 )
■ 예시
class CDainn
{
public:
int m_iA, m_iB;
public:
CDainn() : m_iA(0), m_iB(0) {} // 기본 생성자
CDainn(int _iA, int _iB) : m_iA(_iA), m_iB(_iB) {} // 매개변수 생성자
CDainn operator+(CDainn rhs); // + 덧셈 연산 오버로딩 ( + 기능을 헤치면 안됨 )
// void operator+(CDainn rhs); // 반환타입만 다르기 때문에 함수 오버로딩 불가능
};
[ a + b 자체로 a의 값이 변하지 않도록 하기 위해서 CDainn타입으로 값만 반환 ]
CDainn CDainn::operator+(CDainn rhs) {
CDainn Temp(m_iA + rhs.m_iA, m_iB + rhs.m_iB); // 두 객체를 더한 값으로 새로운 객체 생성
return Temp; // 객체 반환, 지역변수라 임시니까 반환 시 다른 객체가 값을 받아줘야함
}
[ a += b 자체로 a의 값이 변하도록 CDainn& 반환하여 원본값 자체를 변경 ]
CDainn& CDainn::operator+=(CDainn rhs) {
CDainn Temp(m_iA + rhs.m_iA, m_iB + rhs.m_iB); // 두 객체를 더한 값으로 새로운 객체 생성
return Temp; // 객체 반환, 지역변수라 임시니까 반환 시 다른 객체가 값을 받아줘야함
}
* 자신의 멤버 변경 시 굳이 임시 객체 만들어서 반환하지 않고 void 타입으로 대입도 가능
* 다만 단항(++, --) 연산자 오버로딩 시 ++(++객체); 처럼 연속 연산을 원할 경우 문제 발생
void CDainn::operator+(CDainn rhs) {
m_iA += rhs.m_iA;
m_iB += rhs.m_iB;
cout << m_iA << ", " << m_iB << endl;
}
void main(void)
{
CDainn cd1(10, 20);
CDainn cd2(1, 2);
// cd1.operator+(cd2)와 같음, Temp 객체 return
// cd3 = Temp;와 같음 // 대입 연산자 (=)는 디폴트 (1대1 단순대입) 연산자가 있음 - 얕은 복사 주의
CDainn cd3 = cd1 + cd2;
cout << cd3.m_iA << ", " << cd3.m_iB << endl; // public이라 접근 가능한거임 원래 안됨
// 2. 함수 실행 시
/*cout << cd1.m_iA; // 10
cd1 + cd2; // cd1.operator+(cd2); 실행
cout << cd1.m_iA;*/ // m_iA는 cd2.m_iA가 더해진 값인 11이 유지
}
-
// 클래스 CDainn이 존재할 때,
CDainn cd1 = CDainn(100, 'f'); // 오른쪽은 임시 객체
=> cd1.operator=(CDainn _rhs) { 몸체 }; 인 기본 디폴트 연산자가 실행됨
대입 디폴트 연산자 실행 후 복사 생성자 발생 ( 레퍼런스 붙이면 복사 생성자 발생 X 일듯 )
[ 단항 연산자 오버로딩 (전위 / 후의 연산) ]
[ 전위 연산 ] // CObj 클래스 내
CObj& operator ++() // 전위 연산
{ // 선 연산 후 대입
m_iA += 1;
m_iB += 1;
return *this; // 증감된 값을 반환, 레퍼런스로 반환하여 바로 적용시킴
}
[ 후위 연산 ]
CObj operator ++ (int) // 후위 연산, int는 단순 후위연산 표시 용도 ( 매개변수 넣는 거 아님 )
{ // 선 대입 후 연산
CObj Temp(*this); // 복사 생성자 호출
m_iA += 1;
m_iB += 1;
return Temp;
}
=> 똑같은 사본 Temp를 만든 이유는 후위 연산이 실행되는 해당 라인에선
증감되기 전의 값 Temp를 돌려주기 위해서이다. ( 후위연산은 다음 줄에서 증감이 되야하니까 )
void main(void) {
cout << ((++obj2)++).m_iA << "\t" << endl; // 1
cout << ((++obj2)++).m_iB << endl; // 3
cout << (++(++obj2)).m_iC << endl; // 6
}
[ 전위 연산의 경우 void로 반환해도 멤버 값은 ++이 되는데 굳이 레퍼런스로 반환해준 이유 ]
void operator ++() // 전위 연산
{ // 선 연산 후 대입
m_iA += 1;
m_iB += 1;
return; // 증감된 값을 반환, 레퍼런스로 반환하여 바로 적용시킴
}
void main(void) {
CObj cobj;
++cobj; 는 문제없이 가능하지만
++(++cobj); 또는 (++cobj)++; 시 반환값이 없어 연속 증감을 해내지 못한다.
// (cobj++)++;인 연속 후위연산은 원래 불가능한 연산 방법임
}
- 1. 전위 연산을 레퍼런스 타입으로 객체를 반환해야 하는 이유
- 2. 후위 연산에 사본을 만들어주는 이유
함수 객체
[ 함수 객체 ]
: 클래스에 () 연산자를 오버로딩하여 객체를 함수처럼 사용하도록 만드는 문법
: STL에서 사용하는 조건자를 만드는 문법
class CDainn {
public:
CDainn(int _iA) : m_iA(_iA) {} // 생성자
반환타입 operator()(매개변수) { 몸체 } // 함수 객체 (=Functor)
private:
int m_iA;
}
void main(void) {
CDainn cd(10); // 생성자 실행
cd(10); // cd.operator()(10);과 동일, 함수 객체 실행
}
-
[ 연산자 오버로딩과 함수 객체 비교 ]
* 연산자 오버로딩
1. operator뒤에 붙는 연산자(+, -, etc.)와 의도에 맞는 기능을 해야 한다.
2. 매개변수를 하나만 받을 수 있다.
3. 주로 자기자신과 매개변수의 연산을 담당한다.
4. 주로 자기 자신 또는 자신의 객체 타입을 반환한다.
* 함수 객체
1. 매개변수를 여러 개 받을 수 있다.
2. operator() 형태로 기능은 함수처럼 자유자재로 설정한다.
3. 반환타입도 함수의 기능에 따라 자유자재로 설정한다.
■ 기능 / 주의
- 함수 객체는 클래스 내에 존재한다.
- 함수 객체는 인라인 치환을 하여 알고리즘의 조건자로 주로 사용된다. ( 속도 빨라 효율 높음 )
- 함수 객체는 inline 키워드를 명시하지 않아도 컴파일러가 자동으로 인라인 치환 처리 (콜백이 아닐 때) - 함수 객체는 클래스 안에 operator()을 선언하여 함수 모양처럼 사용하는 클래스이다.
- 다른 멤버 변수와 멤버 함수를 가질 수 있어서 일반 함수에서 할 수 없는 시도를 할 수 있다.
- 주로 개별적으로 Functor라는 클래스를 만들어서 함수 객체를 만들어주는 듯하다. - 함수 객체는 인라인 함수 사용을 위해 쓰이는 것!
함수 포인터
[ 함수 포인터]
: 함수를 담는 포인터
반환타입 (*포인터명)(매개변수타입) = 받을함수;
int Sum(int _iA) { return _iA += _iA; )
int Minus(int _iA) { return _iA -= _iA; }
void main(void) {
int (*funcptr)(int) = Sum; // 함수의 이름은 함수 주소, 주소를 받기
funptr = Minus; // 함수 포인터 타입만 일치한다면 다른 함수 대입 받을 수 있음
■ 기능 / 주의
- 함수 포인터는 반환 타입, 매개변수 타입이 같은 함수만 받을 수 있다.
- 콜백 함수 등을 목적으로 사용했는데 지금은 함수 포인터를 거의 사용 안하는 듯하다.
inline
[ 인라인 ]
: 매크로처럼 함수 안의 코드가 치환되는 형태로 사용되는 것 ( 속도 빠름 )
-> 선언부에서 몸체까지 한번에 정의하면 코드(약 8줄 이내)에 따라 컴파일러가 알아서 inline화 (inline 앞에 선언 가능)
-> 함수 오버헤드가 발생하지 않아 속도가 빠른 대신 컴파일된 코드의 크기가 증가한다는 단점이 있음.
■ 기능 / 주의
- 메모리 공간이 할당되지 않고 바로 치환되는 형태이다.
- 함수 포인터를 사용하여 inline 함수의 이름을 초기화하는 경우 inline의 기능은 상실된다.
- inline 함수를 재귀 형태로 호출할 때 inline의 기능을 상실한다.
- 재귀 함수는 메모리 접근 횟수로 판단하는데 inline은 메모리 공간이 할당되지 않으므로 inline화가 풀림 - 컴파일러는 재귀, 긴 함수, static, 반복문, switch문, goto문은 inline을 허용하지 않는다.
'C++' 카테고리의 다른 글
| [TIL 28장] STL 개론(feat. 자료구조), 컨테이너, 시간 복잡도, 벡터 (0) | 2022.09.14 |
|---|---|
| [TIL 27장] 템플릿(함수 템플릿, 클래스 템플릿), 템플릿 특수화(feat. static 템플릿) (0) | 2022.09.13 |
| [TIL 25장] 캐스팅(업캐스팅, 다운캐스팅), 캐스팅 연산자 (0) | 2022.09.08 |
| [TIL 24장] 순수 가상 함수 (0) | 2022.09.08 |
| [TIL 23장] 상속(feat. UML), 객체 포인터, 바인딩(정적 & 동적), 다형성[클래스 속성], 가상함수, 오버라이딩[다형성특징] (0) | 2022.09.06 |