🪄
연산자 오버로딩과 대입연산자
January 02, 2024
연산자 오버로딩과 대입연산자
연산자 오버로딩
- C++에는
+
-
/
*
및>>
<<
와 같은 연산자를 오버로딩하는 기능이 존재한다. - 이를 통해 객체간의 연산을 직관적으로 표현할 수 있다.
멤버 함수를 통한 오버로딩
class Point{
private:
int xpos, ypos;
public:
explicit Point(int x = 0, int y = 0) : xpos(x), ypos(y){
cout<<"Point()"<<endl;
}
void ShowValue(){
cout<<"Value: ["<<xpos<<", "<<ypos<<"]"<<endl;
}
Point operator+(const Point& ref) const{
return Point(xpos+ref.xpos, ypos+ref.ypos);
}
};
int main(){
Point p1(3,4);
Point p2(5,12);
Point p3 = p1+p2;
return 0;
}
p1+p2
는p1.operator+(p2)
와 같다.
- 객체인 Point간의 + 연산을 가능하도록 한다.
전역 함수를 통한 오버로딩
class Point{
private:
int xpos, ypos;
public:
explicit Point(int x = 0, int y = 0) : xpos(x), ypos(y){
cout<<"Point()"<<endl;
}
void ShowValue(){
cout<<"Value: ["<<xpos<<", "<<ypos<<"]"<<endl;
}
friend Point operator+(const Point& p1, const Point& p2);
};
//전역 함수
Point operator+(const Point& p1, const Point& p2){
Point result(p1.xpos + p2.xpos, p1.ypos + p2.ypos);
return result;
}
int main(){
Point p1(3,4);
Point p2(5,12);
Point p3 = p1+p2;
return 0;
}
private 멤버인 xpos, ypos에 접근해야 하므로 friend함수로 정의한다.
- 마찬가지로 객체인 Point간의 + 연산을 가능하도록 한다.
- 전역함수에서는 피연산자 두 개를 모두 인자로 받아야 한다.
cout, cin, endl의 비밀
- cout, cin, endl과
>>
,<<
에도 연산자 오버로딩이 사용된다. - cout는 ostream클래스이며, cin은 istream클래스이다.
ostream의 경우
class ostream{
public:
ostream& operator<<(char * str){
printf("%s", str);
return *this;
}
ostream& operator<<(char str){
printf("%c", str);
return *this;
}
ostream& operator<<(int num){
printf("%d", num);
return *this;
}
ostream& operator<<(double e){
printf("%g", e);
return *this;
}
ostream& operator<<(ostream& (*fp)(ostream &ostm)){
return fp(*this);
}
};
Point 객체에 <<
, >>
오버로딩하기
class Point{
private:
int xpos, ypos;
public:
explicit Point(int x = 0, int y = 0) : xpos(x), ypos(y){
cout<<"Point()"<<endl;
}
void ShowValue(){
cout<<"Value: ["<<xpos<<", "<<ypos<<"]"<<endl;
}
friend ostream& operator<<(ostream& os, Point& ref);
friend istream& operator>>(istream& is, Point& ref);
};
//전역함수
ostream& operator<<(ostream& os, Point& ref){
printf("[%d, %d]", ref.xpos, ref.ypos);
return os;
}
istream& operator>>(istream& is, Point& ref){
scanf("%d %d", &ref.xpos, &ref.ypos);
return is;
}
int main(){
Point p1(1,2);
cout<<p1<<endl;
Point p2;
cout<<"두 수 차례로 입력: ";
cin>>p2;
cout<<p2<<endl;
return 0;
}
<결과>
Point()
[1, 2]
Point()
두 수 차례로 입력: 2 3
[2, 3]
대입연산자
- 대입 연산자는 복사 생성자와 매우 유사하다.
- 정의하지 않으면 디폴트 대입 연산자가 삽입된다.
- 디폴트 대입 연산자는 멤버 대 멤버의 얕은 복사를 진행한다.
- 연산자 내에서 동적 할당을 한다면, 그리고 깊은 복사가 필요하다면 직접 정의해야 한다.
- 객체에 대입 연산자를 오버로딩할때는 얉은 복사/깊은 복사를 신경써야 한다.
- 메모리 누수를 막기 위해 힙에 저장된 데이터는 해제를 해주고 나서 깊은 복사를 시행한다.
예시
Person& operator=(const Person& ref){
delete []name; //누수 방지를 위한 힙 해제
int len = strlen(ref.name)+1;
name = new char[len];
strcpy(name, ref.name);
age = ref.age;
return *this;
}
생성자, 복사생성자, 대입연산자의 호출 시기
- 어느 상황에서 뭐가 호출되는지 헷갈려서 정리함.
class Test {
private:
int m_i;
public:
// 기본 생성자(default constructor)
Test(){
this->m_i = 0;
cout << "call the default constructor" << endl;
}
// 복사 생성자(copy constructor)
Test(const Test &t) {
this->m_i = t.m_i;
cout << "call the copy constructor" << endl;
}
// 복사 대입 연산자(copy assignment operator)
Test& operator=(const Test &t) {
this->m_i = t.m_i;
cout << "call the copy assignment operator" << endl;
return *this;
}
};
int main() {
Test t1; //기본 생성자
Test t2(t1); //복사 생성자
Test t3 = t1; //복사 생성자
t1 = t2; //대입 연산자
return 0;
}
<결과>
call the default constructor
call the copy constructor
call the copy constructor
call the copy assignment operator
대입연산자와 이니셜라이저
- 이니셜라이저를 이용하면 선언과 동시에 초기화가 이뤄지는 형태로 바이너리 코드가 생성된다.
- 따라서 아래와 같은 코드에서 BBB는 복사 생성자만 호출이 된다.
- CCC는 복사 생성자 + 대입 연산자까지 호출이 되어 성능이 더 안좋다.
//BBB의 복사 생성자
BBB(const AAA& ref) : mem(ref) {}
...
//CCC의 복사 생성자
CCC(const AAA& ref) { mem = ref; }
...
//main 함수
AAA obj1(12);
BBB obj2(onj1); //복사 생성자 호출
CCC obj3(obj1);