My Profile Photo

DongChanS's blog


수학과 학생의 개발일지


파이썬 유저를 위한 C++ (9) - 클래스 (3) - 연산자 오버로딩, 프렌드

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. 연산자 오버로딩 시 주의할 점

  1. C++에서 제공되는 연산자만 오버로딩할 수 있습니다.

  2. 기본 자료형(ex, double)간의 연산은 오버로딩할 수 없습니다.

  3. 디폴트 인수 불가능

  4. 오버로드 된 연산자는 비정적(non-static) 클래스 멤버 함수거나 전역 함수이어야 합니다.

  5. 위에서 정의한 경우는 기본적으로 객체의 메서드이기 때문에 순서가 고정됨.

    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 같은 연산을 할 수 있으려면 다음과 같은 조건이 필요합니다.

  • 특정 객체의 메소드가 아니어야함.
  • 그럼에도 불구하고 다른 객체의 멤버변수에 접근할 수 있어야함.
  1. static method

    사실 static method로 선언하면 가능할 것 같지만, 연산자 오버로딩의 규칙에 어긋나기 때문에 불가능합니다.

  2. 프렌드(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::countnamespace만 제한합니다.

    (왜냐하면 기본적으로 파일 영역에서 가지고 있는 변수이기 때문입니다.)

그런데, 문제점은 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
}
comments powered by Disqus