개체지향 프로그래밍2
복사(COPY) 생성자
생성자의 매개변수가 나와 같은 개체라면 복사 생성자라고 생각하면 된다. -> 같은 클래스에 속한 개체를 이용하여 새로운 개체를 초기화한다. -> 말 그대로 복사다! -> 자바 Clone 과 비슷하다~
암시적(implicit) 복사 생성자
코드에 복사 생성자가 없는 경우 컴파일러가 암시적으로 복사 생성자를 생성한다. (기본 생성자가 없는 경우도 동일하다)
암시적 복사 생성자는 얕은 복사(값 복사) 를 수행한다.
개체인 맴버변수는 그 개체의 복사 생성자가 호출된다.
클래스에 포인터 형 변수가 있다면?
얕은 복사이기에 소멸자가 호출되며 복사 된 힙 영역을 반납하는 문제가 생겨버린다. -> 얕은 복사의 문제 -> 복사 생성자를 통해서 힙 영역을 공유하는 것은 상당히 위험하다!
사용자가 만든 복사 생성자
클래스 안에서 동적으로 메모리를 할당하고 있다면!? 위와 같은 상황으로 얕은 복사가 위험할 가능성이 매우 높다!
때문에, 직접 복사 생성자를 만들어서 깊은 복사(deep copy) 를 할 것! -> 포인터 변수가 가리키는 실제 데이터까지도 복사한다.
연산자 오버로딩
연산자를 다시 한번 생각해 보면 함수와 같다! (임의의 INPUT 에 대해 임의의 OUTPUT 을 만들어낸다)
C 와 Java 는 연산자 오버로딩을 지원하지 않는다.
operator(오버로딩 하고자 하는 연산자) 를 기준으로, 좌항은 연산하고자 하는 자기 자신의 개체가 되고 우항은 연산하고자 하는 대상이 된다. -> 아래 예제에서 좌항과 우항의 연산을 통해 새로운 개체를 만들어내기 때문에 매개변수에도 const, 멤버 함수에도 const 키워드를 붙여 주었다.
아래의 연산자 오버로딩을 호출할 때 다음과 같이 호출도 가능하다. (WHY? 연산자도 함수이기 때문에!)
연습문제.1 Vector의 operator+() 연산자를 오버로딩해보자
아래의 코드가 목표이다.
결과물
연산자 오버로딩 시 문제점
예를 들어 연산자 오버로딩을 사용해서 다음과 같은 코드를 만들고 싶다고 가정하자.
하지만 연산자 오버로딩을 해야 하는 std::cout 은 우리가 만든 개체가 아닌, 라이브러리의 개체이다! 그것이 문제인데, 이럴 때 어떻게 문제를 해결할까?
전역 함수로써 연산자 오버로딩의 문제를 해결하자
위와 같은 상황에서 다음과 같이 연산자 오버로딩의 문제를 해결할 수 있다.
매개변수 두 개를 받게 되는데, 하나는 라이브러리 값을 받게 되고, 하나는 사용하고자 하는 개체의 값을 받게 된다.
하지만 아래 코드에도 두 가지 문제가 존재한다.
이 전역 함수를 어디에 넣는가? -> 어느 클래스에도 소속되지 않는다..
전역 함수가 Vector 클래스의 private 멤버를 어떻게 읽는가?
이 때 friend 함수를 쓰면 된다!
friend 키워드의 의미는 다음과 같다.
다른 클래스나 함수가 나의 private 또는 protected 멤버에 접근할 수 있게 허용한다. -> 하지만 자바에서는 이 기능을 OOP 의 캡슐화의 기능을 헤친다고 볼 수 있기 때문에, 안티패턴으로 불리기도 한다..
다음을 통해서 friend 키워드의 예를 살펴보자.
다음과 같이, 클래스가 아닌 함수에 friend 키워드를 사용할 수도 있다.
friend 함수는 멤버 함수가 아니다!
하지만 다른 클래스에서 private, protected 멤버에 접근할 수 있다.
결론적으로 다음과 같이 전역 함수를 사용해 연산자 오버로딩의 문제를 해결할 수 있다.
실제 사용되는 코드를 살펴보자
하지만 이 코드 또한 컴파일 에러가 난다. -> 우리가 만든
void operator<<(const std::ostream& os, const Vector& rhs)``
함수의 반환값이 void 이기 때문이다!
최종적으로 정상적으로 동작하는 코드를 만들어보자. -> 반환값이 void 가 아닌 std::ostream& 이다.
연산자 오버로딩과 const
const 를 쓰는 이유는 무엇일까? 그 이유는 다음과 같다.
멤버 변수의 값이 바뀌는 것을 방지 -> 상식적으로 생각해보자, 개체 A, B 를 연산하는데 A 의 값을 바꾸는 것이 일반적이지는 않다! -> 대부분의 경우 실수다!
최대한 많은 곳에 const 를 붙이자! -> 안전한 코딩을 지향하는 방법
지역(local) 변수에 까지도(모든 회사의 코딩 표준은 아니다..)
매개변수에 const & 를 쓰는 이유는 무엇일까?
불필요한 개체 사본이 생기는 것을 방지한다. -> 값을 바꾸지 않는 것이라면 굳이 복사를 해 메모리를 사용할 필요가 없다.
멤버 변수가 바뀌는 것을 방지한다.
연산자 오버로딩을 남용하지 말아라!
가독성이 심각하게 떨어질 수 있다. -> 코드의 의미를 파악하기가 어려워진다.
이런식이면 차라리 함수를 만들어라! -> 실수를 줄여라!
연산자 오버로딩에서 특별한 operator=
operator= 에 의미는 무엇일까?
생성된 개체에 다른 개체에 대입해준다는 의미 -> 복사생성자와 상당히 비슷하다. -> 차이점이라면, 대입하게 되는 개체가 생성되어 있냐(operator=), 생성하는 중이냐(복사생성자)에 차이이다.
복사 생성자가 컴파일러에 의해 자동으로 만들어졌던 것처럼 operator= 또한 그렇지 않을까? -> 맞다! -> 하지만, 얕은 복사(shallow copy) 이기 때문에 직접 힙 메모리를 사용하는 경우에는, 직접 구현이 필요하다!
암시적 함수들을 제거하는 법!
클래스를 생성시 딸려오는 기본 함수들은 다음과 같다.
매개변수 없는 생성자
복사 생성자
소멸자
대입(=) 연산자
하나씩 지워보자
기본생성자를 지우는 법
생성자를 만들거나
기본생성자를 private 에서 만들어주자
복사생성자를 지우는 법
복사생성자를 private 에서 만들어주자
소멸자를 지우는 법
소멸자를 private 에서 만들어주자 -> 하지만 위의 경우, 개체를 사용하고 난 후 소멸자를 호출 할 수 없기 때문에, 컴파일 에러가 발생한다.
대입(=) 연산자를 지우는 법
대입 연산자를 private 에서 만들어주자
Last updated