1) 컴포지션과 어그리게이션
- 다중상속: 부모클래스를 여러개 상속받아 자식클래스를 정의하는 것
상속받고자 하는 클래스들을 ,로 구분해서 나열하면 됨
상속 접근지정자를 명시하지 않으면 private로 지정- 다중상속의 단점: 다중상속이 많아질수록 클래스가 커지고 (결합도 증가로 인한 유지/ 보수 문제 증가), 컴파일 시간이 늘어남
- 다중상속 대안: 다중상속으로 코드를 재사용하기보단, 재사용할 속성과 기능을 별도의 클래스로 분리하고 그 클래스를 멤버변수로 포함 => 단일 속성과 기능을 가지기 때문에 결합도가 낮아지고, 변경의 영향이 다른 클래스로 넘어가지 않음
- 컴포지션: 분리한 클래스를 포함
- 어그리게이션: 분리한 클래스를 사용
=> 객체의 생명주기를 비교해 구별
- 객체의 생명주기: 객체가 생성되어 소멸하는 과정
- 컴포지션 (part-of): 클래스가 가져야 할 특징을 다른 클래스로부터 상속받는 것이 아닌 멤버 변수로 포함하는 개념= 재사용할 속성과 기능을 별도의 클래스로 분리하고 이 클래스의 객체를 멤버 변수로 포함하는 것
= 별도로 분리한 클래스가 멤버변수로 포함한 클래스에 종속되고, 분리한 클래스의 객체는 이를 멤버변수로 포함한 클래스에서 생명주기를 직접 관리하므로 포함관계, 두 클래스의 생명주기는 같아짐
= 다중상속의 단점을 보완하고, 다형성 구성 측면에서 상속보다 더 유리- 상속과 컴포지션의 차이: 코드 분리여부
- 어그리게이션 (has-a): 재사용할 속성과 기능을 별도의 클래스로 분리하고 그 클래스의객체를 멤버변수로 포함. 단, 분리된 클래스의 객체를 포인터나 레퍼런스로 포함 (레퍼런스로 포함시 생성자에서 초기화)
= 분리된 클래스와 이를 포함하는 클래스의 생명주기가 다름
= 분리된 클래스를 사용하는 개념이므로 is-a관계가 아닌 has-a관계
= 분리된 클래스와 그를 사용하는 클래스가 서로 유연한 관계를 가지기 때문에 분리된 클래스를 직접 참조할 수도 있고 해당 클래스를 상속받은 자식클래스를 참조할 수도 있음
+) 상속은 부모클래스를 자식클래스가 대체할 수 있어야 할 때 사용
컴포지션은 클래스의 공통 기능이나 단위기능을 클래스로 분리할 때 사용
+) 컴포지션과 어그리게이션의 중요한 구별점은 생명주기
2) 가상함수와 동적 바인딩
- 가상함수: 멤버함수 가운데 자식 클래스에서 오버라이딩해야 하는 함수를 선언할 때 사용
(일반 함수도 자식클래스에서 오버라이딩 할 수 있지만, 부모클래스로 형식이 변환되었을 때 (업캐스팅) 호출되는 함수에 차이 발생)- 가상함수 선언:
virtual 반환형식 함수이름 (매개변수);
자식클래스에서 가상함수를 오버라이딩할 때도 똑같은 선언문을 사용 (override 키워드는 추가하지 않아도 됨)- 가상함수 오버라이딩 선언:
virtual 반환형식 함수이름 (매개변수) override;
- 가상함수로 다형성 구현 가능
- 함수의 동적바인딩
바인딩: 프로그램을 실행하고 함수호출이나 변수참조가 해당 코드와 연결되는 과정
- 정적바인딩 (이른바인딩): 컴파일 시점에 동작하며, 프로그램이 실행되는 동안 그대로 유지 (일반 변수, 함수, 클래스, 정적 멤버함수, 템플릿 등을 정적으로 바인딩)
정적바인딩이라고 해서 항상 같은 메모리주소가 저장되는 것이 아니라 바인딩 대상이 컴파일 시점에 결정되는 것
=> 실제 참조할 메모리주소는 프로그램을 실행할 때마다 달라짐,, 따라서 정적바인딩은 실제 메모리 주소를 고정하는 것이 아니라 바인딩 대상이 있는 위치를 고정하는 것
=> 고정된 대상을 변경하려면 소스코드 수정 후 다시 컴파일
- 동적바인딩 (늦은바인딩): 대상이 실행되는 시점에 결정되며, 바뀔 수 있음
(가상함수, 자식클래스로 치환된 부모클래스의 포인터가 동적으로 바인딩)
동적으로 바인딩된 함수는 호출 대상이 바뀔 수 있음 (바인딩 대상 변경)
- 가상함수 테이블: 가상함수의 위치 저장
가상함수가 있는 클래스로 객체를 생성하면 메모리에 _vfptr이라는 가상함수 테이블을 가리키는 포인터가 자동으로 생기고, 객체가 가상함수를 호출할 때 _vfptr사용
= 클래스의 계층 구조에서 최상위 클래스에만 존재 (자식클래스는 부모클래스의 가상함수 테이블 상속, 가상함수를 오버라이딩할 때 해당 함수의 주소를 가상함수 테이블에 등록)
=> 가상함수 호출될 때 가상함수 테이블 사용
- 순수 가상함수: 부모클래스에서는 가상함수를 선언만 하고 자식클래스에서 정의하도록 강제하는 함수 => 부모클래스에서는 동작하지 않지만 자식클래스의 기능을 미리 선언하고 싶을 때 사용
- 순수 가상함수 선언:
virtual void attack_special (player target_player) =0;
순수 가상함수를 선언하려면 =0을 추가해야 함 (클래스에서 가상함수 선언만 하고 정의하지 않으면 오류 발생)- 선언한 이후에는 이 클래스를 상속받는 자식클래스에서 반드시 정의해야함
만약 해당 자식클래스에서도 정의하지 않고 자식클래스를 상속하는 또 다른 자식클래스에서 정의하게 하려면 또 순수 가상함수로 선언
- 가상소멸자
소멸자를 가상함수로 정의하면 업캐스팅 상황에서도 자식클래스에 정의된 소멸자를 호출할 수 있음
=> 업캐스팅된 객체에서 재정의된 함수를 호출할 수 있음 (클래스의 생성자와 소멸자는 특별한 함수여서 자식클래스에서 정의한 생성자와 소멸자는 이름이 달라도 부모클래스의 생성자와 소멸자를 재정의한 것으로 여김)
=> 가상소멸자를 사용하지 않으면 업캐스팅된 클래스의 객체를 생성했을 때 부모클래스의 소멸자만 호출되어 자식클래스의 객체에 필요한 후처리를 할 수 없음
3) 추상클래스와 정적멤버
- 추상클래스 (인터페이스): 추상적인 클래스 (!= 구체적이지 않은...)
클래스에 순수 가상함수가 있거나, 일반함수나 멤버변수를 가지고 있어도 순수 가상함수가 있다면 추상클래스
=> 객체 선언 불가능, 추상클래스를 상속받은 자식클래스에서 객체 선언
자식클래스에서는 순수 가상함수를 반드시 오버라이딩 해야 함
다형성 구현시 사용, 추상클래스에는 담당할 함수만 선언하고 여러 자식클래스에서 각기 다른 알고리즘으로 동작하도록 정의- 전략패턴: 추상클래스에는 담당할 함수만 선언하고 여러 자식클래스에서 각기 다른 알고리즘으로 동작하도록 정의하는 것
같은 기능을 다른 알고리즘으로 정의하고 싶을 때 각각을 캡슐화하여 교체할 수 있게 함 => 유지/보수성 향상
- 정적멤버: static 키워드 사용
정적멤버는 클래스에 속하지만 특정 인스턴스에 종속되지 않으므로 메모리에 한 번만 할당되고, 모든 인스턴스가 공유 가능 => 클래스로 선언하는 객체와는 독립적
=> 외부에서 정적 멤버변수를 사용하거나 정적 멤버함수를 호출할 때는 범위연산자 ::로 해당 클래스의 이름을 붙여 소속을 밝혀야 함
객체를 생성했다고 해서 객체이름으로 사용하는 것이 아닌, 클래스 이름도 붙여야 함
class name:: static_variable =10;
class name:: static_function();
정적 멤버도 클래스에 속하는 것이므로 사용 범위는 일반 멤버처럼 클래스에서 지정한 접근 지정자를 따름 => 접근지정자로 사용 범위 통제 가능, 클래스로 캡슐화 가능
(전역변수, 함수와 다른 점)
=> 정적멤버
- 클래스당 한번만 메모리에 할당
해당 클래스로 선언한 모든 인스턴스가 공유
소속클래스를 범위지정자로 붙여 사용
정적 멤버변수는 클래스 안에 선언하고 밖에서 초기화
정적멤버 함수는 클래스 안에 정의해도 메모리에는 별도로 존재하므로 인스턴스에 독립적class monster { public: static monster* create_monster(); //정적 멤버함수 선언 private: static int mon_count; //정적 멤버변수 선언 }; int monster:: mon_count=0; //정적 멤버변수 초기화=> 클래스에서 어떤 자료는 모든 객체가 공유해야할 때 정적 멤버변수로 선언, 클래스의 객체를 클래스 내부에서 직접 관리하고 싶을 때 정적 멤버함수, 유틸리티 함수 (문자열 조작, 날짜/ 시간계산, 파일 입출력 등 특정 작업을 지원하는 함수로, 클래스나 객체에 독립적으로 재사용과 유지/ 보수에 유용) 제작 시 유용
+) 팩토리 패턴을 사용하면 객체를 일정한 규칙으로 생성 가능, 캡슐화 통해 시스템의 확장성과 변화에 유연하게 대처 가능
+) 추상클래스= 순수 가상함수가 포함된 클래스, 추상클래스로 객체 선언 불가 (순수 가상함수의 정의가 없어 메모리에 인스턴스 생성 불가)
+) 정적클래스 멤버는 객체 생성 없이 함수와 변수를 사용할 수 있으며 전역변수/ 함수와 다르게 접근지정자를 통해 클래스 외부에서 사용할 수 없도록 강제할 수 있음