주제
- 서브타입 다형성
- 추상 클래스
- 인터페이스
Ex00
주요 개념
다형성 (polymorphism)
- 임시 다형성 (ad-hoc polymorphism)
- overloading
- 서브타입 다형성 (subtype polymorphism)
- overriding
- 매개변수화한 다형성 (parametric polymorphism)
- 템플릿의 사용
가상 함수
- 파생 클래스에서 재정의할 것으로 기대하고 정의한 클래스
- 순수 가상함수
- 기반 클래스에서는 별도의 정의를 하지 않는 클래스
- 함수 선언 뒤에
= 0
을 붙여서 선언
- 동적 바인딩이 되어 런타임에 어떤 함수를 호출할 지가 결정된다.
- vs 정적 바인딩
- 컴파일 타임에 어떤 함수를 호출할 지를 알 수 있다.
- 예시) 가상 함수 외의 함수들
- 예시
다음의 두 클래스 B, C(B를 상속)가 있다고 하자.
1 2 3 4 5 6 7 8 9 10 11
#include <iostream> class B { public: virtual void bar(); virtual void qux(); }; void B::bar() {std::cout << "This is B's implementation of bar" << std::endl;} void B::qux() {std::cout << "This is B's implementation of qux" << std::endl;}
1 2 3 4 5 6
class C : public B { public: void bar() override; }; void C::bar() {std::cout << "This is C's implementation of bar" << std::endl;}
아래와 같이 C의 객체가 함수를 호출한다.
1 2
B* b = new C(); b->bar();
b는
C::bar()
를 호출한다.
- 원리
- vs 정적 바인딩
Q&A
다음과 같이 호출할 때 복사 생성이 아닌, 디폴트 생성자가 호출되며, 아래에서 delete하게 되면 아래와 같은 에러 발생.
1 2
const Animal *j = new Dog(); const Animal *i(j);
1
a.out(45439,0x10d40ddc0) malloc: *** error for object 0x7f9df9c05870: pointer being freed was not allocated
- 복사생성자는 포인터를 인자로 받지 않기 때문
파생 클래스에서 복사 생성자를 정의할 때 에러 발생.
1 2 3 4 5 6
Cat.cpp: In copy constructor ‘Cat::Cat(const Cat&)’: Cat.cpp:14:1: error: base class ‘class Animal’ should be explicitly initialized in the copy constructor [-Werror=extra] 14 | Cat::Cat(const Cat& origin) | ^~~ cc1plus: all warnings being treated as errors make: *** [Makefile:39: obj/Cat.o] Error 1
1 2 3 4 5
Cat::Cat(const Cat& origin) { _type = origin._type; std::cout << C_NAME << " Copy constructor called" << std::endl; }
- 파생 클래스에서 복사 생성자를 정의할 때는 기반 클래스의 생성자를 명시적으로 호출해야한다. 그렇지 않으면 기반 클래스의 기본 생성자가 호출되어 변수의 초기화가 제대로 이루어지지 않을 수 있다.
1 2 3 4 5 6
Cat::Cat(const Cat& origin) : Animal(origin) { _type = origin._type; std::cout << C_NAME << " Copy constructor called" << std::endl; }
Ex01
- 파생 클래스에서 복사 생성자를 정의할 때는 기반 클래스의 생성자를 명시적으로 호출해야한다. 그렇지 않으면 기반 클래스의 기본 생성자가 호출되어 변수의 초기화가 제대로 이루어지지 않을 수 있다.
주요 개념
얕은 복사 vs 깊은 복사
- 얕은 복사 (shallow copy)
- 원본 객체의 참조자로 새로운 객체를 생성하는 방식
- 사본에 변동이 생기면 원본에도 반영된다.
- 깊은 복사 (deep copy)
- 원본과 완전하게 독립적인 사본을 생성하는 방식
- 사본의 변경사항이 원본에 영향을 미치지 않는다.
오버로딩 vs 오버라이딩
두 개념 모두 다형성에 관련된 개념이며 클래스에서 동일한 이름의 함수를 여러개 만드는데 사용된다.
- 오버로딩(overloading)
동일한 함수명
을 가졌지만매개변수가 다른
함수를 여러 개 정의할 수 있는 기능.1 2 3 4 5 6 7 8 9 10
class MyClass { public: int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; } };
- 오버라이딩(overriding)
파생 클래스
가 기반 클래스의가상 함수
를다시 정의
하는 것.- 함수를 파생 클래스에 맞추어
구체적으로 구현
하기 위한 목적으로 주로 사용됨.
1 2 3 4 5 6 7 8 9 10 11 12 13
class Base { public: virtual void display() { std::cout << "Base" << std::endl; } }; class Derived : public Base { public: void display() { std::cout << "Derived" << std::endl; } };
- 함수를 파생 클래스에 맞추어
과제 조건
- Brain
- string 배열
- Dog, Cat
- dog, cat이 모두
brain*
를 가져야 함.- 생성될 때 brain을 동적할당
- 복사 생성은 깊은 복사로 수행할 것
- dog, cat이 모두
Q&A
- 생성자 내부에서 변수를 초기화하는것에 비해 초기화 리스트가 같는 이점?
- 변수를 생성자 내부에서 초기화하는 방식은 생성자 호출 후 할당하게 되어 비효율적이다.
- void func(const int &index)와 같이
const, 참조자
를 사용하는 이유- 참조자 사용 이유 : 함수를 호출할 때 메모리 상에 복사를 하지 않고 해당 변수의 주소를 통해 원본을 넘겨줄 수 있다. (메모리 측면에서 효율적)
- const 사용 이유 : 원본을 넘겨주기 때문에 값의 변경이 이루어질 수 있기 때문에 const를 통해 원본의 변경을 막는다.
- 복사 생성자, 대입 연산자에서 brain을 동적할당하는 여부가 다른 이유?
복사 생성자
1
_brain = new Brain(*origin._brain);
대입 연산자
1
*_brain = *(origin._brain);
- 복사 생성자는 객체를 새로 생성하는 것이다.
- 생성 + 초기화(변수의 값을 복사)
- 반면 대입 연산자는 기존에 이미 생성된 객체에 멤버 변수를 복사해주는 것이다.
- 변수의 값 복사만 이루어진다.
Ex02
주요 개념
추상 클래스 (Abstract class)
- 특징
- 사용이 불가능한 경우
- 변수 또는 멤버 변수
- 함수의 전달되는 인수 타입
- 함수의 반환 타입
- 명시적 타입 변환의 타입
과제 조건
- Animal 클래스를 추상 클래스로 만들기
- 불완전한 Animal 클래스가 인스턴스화되는 것을 막기 위해
- 방법 : 하나 이상의 순수 가상 함수를 포함시킨다.
Ex03
주요 개념
- concrete class
- 순수 가상 함수가 없는 클래스
- 추상 클래스의 반대 개념
- concrete : 실체가 있는
- 인터페이스
- 소멸자와 순수 가상함수만 선언된 클래스
구현 사항
- 클래스
- AMateria : Ice, Cure의 기반 클래스
- Ice
- Cure
- ICharacter : Character의 인터페이스
- Character
- IMateriaSource : MateriaSource의 인터페이스
- MateriaSource
- AMateria : Ice, Cure의 기반 클래스
- 과제 설명
- AMateria는 모든 무기를 아우르는 개념
- 무기는 Ice(공격용), Cure(치료용)이 있다.
- MateriaSource는 무기 상점 개념
- 상점에는 최대 4개의 무기 전시대가 있다.
- 무기 객체를 받아서 learn하면 무기 상점의 전시대 하나에 해당 무기를 전시한다.
- ice, cure 중 하나를 create하면 전시대에 존재하는지 확인 후에 3D 프린터로 복사해서 준다.
- Character는 게임 캐릭터 개념
- 한 캐릭터가 최대 4개의 무기를 장착할 수 있다.
- 상점에서 받아온 무기를 equip, unequip해서 장착/제거 할 수 있다.
- use하여 공격 또는 치료를 할 수 있다.
- AMateria는 모든 무기를 아우르는 개념
Q&A
- 인터페이스 ICharacter의 프로토타입을 AMateria 상단에 선언하는 이유?
- 헤더를 include하는 대신 전방 선언하여 순환 참조(circular dependency)를 막는다
- 전방 선언(forward declaration) : 식별자의 존재를 컴파일러에게 미리 알리는 것.
#ifndef
전처리 지시어- 헤더가 중복 include되는 것을 막아준다.
- 순환 참조 문제는 해결이 안되기에 전방 선언이 필요하다.
- 헤더를 include하는 대신 전방 선언하여 순환 참조(circular dependency)를 막는다
기타
Call by value vs Call by reference
- Call by value
- 인자의 사본을 함수에 넘겨주는 방식
- 함수 내에서 인자를 변경해도 밖에는 영향을 미치지 않는다.
- Call by reference
- 원본 인자를 전달하는 방식
- 함수 내에서 인자를 변경하면 밖에도 영향을 미친다.
- 결론
- call by value : 인자를 복사해서 전달해야 하는 경우
- call by reference : 인자의 참조자를 넘겨줘야 하는 경우
- 이 방식이 인자를 복사하는데 필요한 오버헤드가 없어서 효율적이다.