[CPP-Module/ex00: My First Canonical Class 과제]
You are going to discover a new and awesome number type: fixed point numbers! Write a canonical Fixed class to represent fixed point numbers:
Private members:
int : 고정 소수값을 저장static const int : 항상 8 글자의 값을 가지는 fractional bits가 저장Public members:
From now on, each class you write MUST be in canonical (Coplien) form: At least one default constructor, a copy contructor, an assignation operator overload and a destructor. We won’t ask again.
canonical이란, "규정대로"하는 프로그래밍을 의미하며, 반대로 non-canonical이란 "규정에 따르지 않고"하는 프로그래밍을 의미한다. 일종의 코딩 컨벤션같다. 앞으로는 과제를 진행할 때 Coplien form 규정대로 클래스를 작성해야한다.
위 네 가지 멤버는 그것이 사용되던 안되던 항상 클래스에 정의되어야 한다. 생성자와 소멸자는 알겠는데 복사 생성자와 대입 연산자 오버로딩은 생소한 개념이다.
Fixed::Fixed(const Fixed& fixed) //선언
Fixed a;
Fixed b(a); // a를 복사해 b를 생성했다.
상수로 선언된 참조자이며, type은 클래스(자기 자신)가 된다. 복사 생성자는 기존 값을 복사해 전달해주는 개념이라 값이 바뀌어서는 안된다. 따라서 const로 선언한다.
얕은 복사를 한다. 깊은 복사인 것은 아니다. 깊은 복사를 하고 싶다면 복사 생성자 내에 동적 할당된 새 객체가 있어야 한다. CPP에는 오버로드라는 개념이 존재한다. C와는 다르게, 함수의 이름이 같아도 매개변수가 다르면 선언 및 정의가 가능하다. 지금까지 이 개념을 이용해 조건에 따라 다른 기능을 하도록 생성자를 여러개 만들 수 있었던 것이다. 즉 오버로드는, 같은 객체지만 다른 기능을 하도록 만든다는 의미다.
그렇다면 연산자 오버 로딩이란 기존의 연산자를 재정의하는 것이라고도 볼 수 있다. 원래 객체끼리는 + 같은 연산자를 사용할 수 없는 것이 일반적이다. 하지만 연산자 오버로딩을 통해 +의 기능을 재정의 해준다면, 객체가 기본 자료형 변수처럼 덧셈, 뺄셈, 혹은 곱셈과 같은 연산들을 할 수 있도록 만들 수 있다. 이렇게 되면 객체도 기본 자료형 데이터처럼 취급하는 것이 가능하다.
Fixed& operator=(const Fixed& fixed);
연산자 오버 로딩을 하기 위해서는 operator 키워드와 연산자를 묶어서 함수의 이름을 정의하면, 연산자를 이용한 함수의 호출이 가능하다.
위 예제처럼 클래스 내부에 연산자 오버로딩을 선언하면, 클래스 멤버 함수에서 오버로딩된 = 을 사용할 수 있다.
예를 들어 아래 복사 생성자
Fixed::Fixed(const Fixed& fixed)
{
std::cout << "Copy constructor called" << std::endl;
*this = fixed;
}
*this = fixed; 는 왼쪽의 *this 객체를 대상으로 operator= 함수를 호출하면서 오른쪽 피연산자인 객체 fixed를 인자로 전달한다는 의미를 가진다.
얕은 복사를 한다.아니 그러면, 이 프로그램 내의 모든 코드에서 = 연산이 재정의 되는건가? 하는 의문을 가질 수 있다. 원래 기능인 대입 연산도 필요하고, 객체를 복사할 때도 = 가 필요한데 말이다.
사실, 복사 생성자와 대입 연산자 오버로딩은 유사하지만 호출되는 시점이 다르다. 복사 생성자는 객체가 새로 생성되는 시점에서 대입을 할 때 호출이 되고 대입 연산자 오버로드는 객체 두 개가 이미 생성 및 초기화 진행된 상태에서 올바른 인자를 대입 할 때 호출이 된다.
즉, 연산자 오버로딩 함수의 매개변수를 특정 객체로 선언했다면, = 연산자 오른쪽에 그 객체가 들어왔을 때만 연산자가 오버로드 된다.
연산자 내에 동적 할당이 된 경우에는 이전에 동적 할당되었던 데이터를 해제해야 한다. 그렇지 않으면 기존에 있던 데이터가 소멸되지 않아 메모리 누수가 생긴다.
int main(void)
{
Fixed a;
Fixed b(a);
Fixed c;
c = b;
std::cout << a.getRawBits() << std::endl;
std::cout << b.getRawBits() << std::endl;
std::cout << c.getRawBits() << std::endl;
return 0;
}
//console
Default constructor called //a 생성
Copy constructor called // b 생성
Assignation operator called // *this = fixed;
getRawBits member function called // fixed.getRawBits();
Default constructor called //c 생성
Assignation operator called //c = b
getRawBits member function called // fixed.getRawBits();
getRawBits member function called
0
getRawBits member function called
0
getRawBits member function called
0
Destructor called
Destructor called
Destructor called