12) 클래스 (3)
12-1. 연산자 오버로딩
파이썬에서는 __add__
, __sub__
와 같은 매직 메서드를 통해서 객체를 이용한 연산자를 정의할 수 있습니다.
기본적으로는 이러한 객체간의 연산이 정의되어 있지 않기 때문에, my_book + your_book
같은 시도를 하면 에러가 납니다.
- 문법
Book operator+(const Book& another_book)
+
연산자 이외에도 기본적으로 정의된 연산자는 대부분 오버로딩이 가능합니다.
- 정의 예시
const Book Book::operator+(const Book& another_book){
this->price_ += another_book.price_;
this->page_ += another_book.page_;
return (*this);
}
- 호출 예시
int main(){
Book my_book = Book(5000, 100);
Book your_book = Book(6000, 120);
Book double_book = my_book + your_book;
}
12-2. 연산자 오버로딩 시 주의할 점
-
C++에서 제공되는 연산자만 오버로딩할 수 있습니다.
-
기본 자료형(ex, double)간의 연산은 오버로딩할 수 없습니다.
-
디폴트 인수 불가능
-
오버로드 된 연산자는 비정적(non-static) 클래스 멤버 함수거나 전역 함수이어야 합니다.
-
위에서 정의한 경우는 기본적으로 객체의 메서드이기 때문에 순서가 고정됨.
ex)
객체 * 3
을 정의했다면3 * 객체
도 가능해야하는데, 그렇지 않다는 뜻입니다.
const Book Book::operator*(int mul){
this->price_ *= mul ;
this->page_ *= mul ;
return (*this);
}
int main(){
Book my_book = Book(5000, 100);
Book many_book = 10 * my_book; // 에러!
}
출력결과
prog.cc: In function 'int main()':
prog.cc:33:8: error: no match for 'operator*' (operand types are 'int' and 'Book')
33 | 10 * my_book;
| ~~ ^ ~~~~~~~
| | |
| int Book
이런 문제를 C++의 프렌드라는 개념을 통해서 해결할 수 있습니다.
12-3. 프렌드 (friend) 함수
일단 10 * my_book
같은 연산을 할 수 있으려면 다음과 같은 조건이 필요합니다.
- 특정 객체의 메소드가 아니어야함.
- 그럼에도 불구하고 다른 객체의 멤버변수에 접근할 수 있어야함.
-
static method
사실 static method로 선언하면 가능할 것 같지만, 연산자 오버로딩의 규칙에 어긋나기 때문에 불가능합니다.
-
프렌드(friend) 함수
-
클래스 선언부에 원형이 포함되지만, 클래스의 멤버함수는 아님.
- 그렇지만 멤버함수와 같은 접근권한을 가짐.
friend
라는 키워드를 통해서 설정합니다.
-
예시) 정수 * 객체
연산자를 구현하기
#include <iostream>
class Book{
private:
int price_;
int page_;
public:
Book(int price, int page);
friend Book& operator*(int mul, Book& book);
// 선언할때는 friend, 전역이므로 public, private 어디든 설정해도 상관없음.
void print();
};
int main(){
Book my_book(10000, 100);
Book multiple_books = 10 * my_book;
multiple_books.print();
}
Book& operator*(int mul, Book& book){ // 전역함수니까 namespace 지정안함.
book.price_ *= mul;
book.page_ *= mul;
return book;
} // friend 키워드는 함수 선언시에만 지정합니다.
12-4. 프렌드 클래스
프렌드 함수와 비슷한 개념입니다.
프렌드 클래스는,
- 어떤 클래스 내부에 friend 키워드를 이용하여 선언함
- 프렌드 클래스는 그 클래스의 모든 멤버에 접근이 가능함.
예시) Store 클래스에서 Book 클래스의 private 멤버에 접근하는 예시
class Book{
private:
int price_;
int page_;
public:
Book(int price, int page);
friend class Store;
};
class Store{
private:
int id = 12345;
public:
void printBookInfo(Book& book);
};
void Store::printBookInfo(Book& book){
std::cout << "price :" << book.price_ << " page :" << book.page_;
}
int main(){
Store store;
Book my_book(10000,100);
store.printBookInfo(my_book);
}
사실 friend 라는 뜻이 친구니까.. 양방향으로 작용하는 것처럼 보이지만, 한쪽 방향으로만 작동합니다.
ex) Book 클래스 선언부에서 friend class Store
를 지정하면,
- Book의 private 멤버들을 Store에서 접근가능
- but, Store의 private 멤버들을 Book에서는 접근할 수 없음.
12-5. static 멤버
파이썬에서는 클래스명.멤버이름
의 형태로, 클래스 단위에서 관리하는 클래스 멤버(변수, 메소드)가 있었습니다.
그렇기 때문에 클래스의 모든 인스턴스가 클래스 멤버의 값을 공유하게 됩니다.
C++에서의 static 멤버는 이와 유사한 기능을 가집니다.
-
static 키워드를 이용하여 선언
-
우리는 클래스 내부에서 선언하지만, 실제로는 파일 영역에서 수행됨.
=> 여러 파일에서 static 멤버에 접근할 수 있습니다.
ex) 객체의 숫자를 관리하는 static 변수
class Person{
private:
static int count;
public:
Person();
static void getCount();
};
int Person::count = 0; // 초기화
Person::Person(){
Person::count += 1; // 생성자
}
void Person::getCount(){
std::cout << Person::count;
}
int main(){
Person people[10];
for (int i=0; i<10; i++){
Person person;
people[i] = person;
}
Person::getCount(); // 20
}
문법을 보시면 파이썬과 몇가지 차이점이 있습니다.
-
static 변수는 클래스 선언부에서 초기화가 불가능!
(constant static이면 가능함.)
-
그래서 클래스 선언부 바깥에서 클래스 변수를 초기화해주어야 합니다.
-
Person.count
같은 형식으로 접근하지 않고,Person::count
로 namespace만 제한합니다.(왜냐하면 기본적으로 파일 영역에서 가지고 있는 변수이기 때문입니다.)
그런데, 문제점은 10개의 객체만 생성했는데 20으로 출력됩니다.
왜냐하면 people[i] = person
을 하기 위해서 객체가 복사되었기 때문입니다.
현재 메모리에 남아있는 person 객체의 개수를 반영하려면, 객체가 사라질 때 count를 감소시켜야합니다.
ex) 소멸자를 이용해서 카운팅
Person::~Person(){
Person::count -= 1;
}
int main(){
Person people[10];
for (int i=0; i<10; i++){
Person person;
people[i] = person;
}
Person::getCount(); // 10
}