- 함수 오버로딩(Overloading)
같은 이름의 함수가 여러개 정의되어있고, 매개 변수의 타입, 개수, 순서가 다른 경우 컴파일러는 함수 호출 시 인자 타입을 보고 함수를 선택(정적 바인딩)- 함수 오버라이딩(Overriding)
부모 클래스의 함수를 자식 클래스에서 동일한 이름, 매개변수, 반환타입으로 재정의
부모 클래스의 함수가 가상함수(virtual)여야 함
class Animal {
public:
virtual void makeSound() { std::cout << "Animal sound" << std::endl; }
};
파생 클래스에서 재정의할 것으로 예상되는 함수에 virtual 키워드를 붙여 선언,
Animal* pet = new Dog();
pet->makeSound(); // virtual 없으면 "Animal sound" 출력
정적 바인딩(Static Binding):
virtual 키워드가 없을 때 발생
컴파일 시점에 호출할 함수 결정
포인터/참조 타입에 따라 결정
Animal* pet = new Dog();
pet->makeSound(); // virtual 있으면 "Woof!" 출력
동적 바인딩(Dynamic Binding):
virtual 키워드가 있을 때 발생
런타임에 객체의 실제 타입에 따라 함수 결정
객체의 동적 타입에 따라 결정
순수 가상 함수와 추상 클래스
class AAnimal {
public:
virtual void makeSound() = 0; // 순수 가상 함수
};
구현 없이 인터페이스만 제공
파생 클래스에서 반드시 구현해야 함
하나 이상의 순수 가상 함수가 있는 클래스는 추상 클래스가 됨
추상 클래스는 직접 인스턴스화할 수 없음
소멸자와 가상함수
소멸자도 가상 함수로 선언해야하는 경우가 많다
class Animal {
public:
virtual ~Animal() { std::cout << "Animal destructor" << std::endl; }
};
부모 포인터로 자식 객체를 삭제할 때, 자식의 소멸자가 호출되도록 함
가상 소멸자가 없으면 자식 클래스의 리소스 해제가 올바르게 이루어지지 않음
class Animal {
protected:
std::string _type;
public:
Animal();
virtual ~Animal();
virtual void makeSound() const;
std::string getType() const;
};
가상함수는 부모 클래스에서 상속받을 클래스에서 재정의 할 것으로 기대하고 정의해 놓은 함수
virtual를 함수 앞에 붙여 생성된 가상함수는 파생 클래스에서 재정의 하면 이전에 정의 되었던 내용들은 새롭게 정의된 내용들로 교체된다.
virtual 키워드가 있으면 동적 바인딩이 발생한다.
런타임에 객체의 실제 타입(Derived)에 따라 호출될 함수가 결정된다.
일반 오버라이딩: 정적 바인딩, 컴파일 시점에 결정, 포인터/참조 타입 기준
가상 함수 오버라이딩: 동적 바인딩, 런타임에 결정, 객체의 실제 타입 기준
class Dog : public Animal {
public:
Dog();
~Dog();
void makeSound() const; // 오버라이딩
};
class Cat : public Animal {
public:
Cat();
~Cat();
void makeSound() const; // 오버라이딩
};
Animal 클래스 상속
makeSound() 메서드 오버라이딩: Dog는 "Woof!", Cat은 "Meow!"
private:
std::string _ideas[100]; // 생각을 저장할 배열
public:
Brain();
Brain(const Brain &other);
Brain &operator=(const Brain &obj);
~Brain();
void setidea(std::string str);
std::string *getidea();
};
class Dog : public Animal {
private:
Brain *_brain; // 동적 할당된 Brain 객체
public:
Dog();
Dog(const Dog &other);
Dog &operator=(const Dog &obj);
~Dog();
void makeSound() const;
Brain *getBrain();
};
Dog::Dog(const Dog &other) {
_brain = new Brain(); // 먼저 새 Brain 할당
*this = other; // 대입 연산자 호출
}
Dog &Dog::operator=(const Dog &obj) {
if (this != &obj) {
_type = obj._type;
if(_brain)
delete _brain; // 기존 Brain 삭제
_brain = new Brain; // 새 Brain 할당
*_brain = *(obj._brain); // Brain 내용 복사
}
return (*this);
}
Dog::~Dog() {
delete _brain; // 동적 할당된 메모리 해제
}
class AAnimal {
protected:
std::string _type;
public:
AAnimal();
virtual ~AAnimal();
virtual void makeSound() const = 0; // 순수 가상 함수
std::string getType() const;
};
virtual void makeSound() const = 0;
순수 가상 함수로 선언
이 클래스는 인스턴스화할 수 없고, 상속받는 클래스에서 반드시 구현해야 함
class Dog : public AAnimal {
private:
Brain *_brain;
public:
Dog();
~Dog();
void makeSound() const; // 순수 가상 함수 구현 필수
};
Animal 클래스에서 makeSound() 함수를 virtual로 선언한 경우
int main() {
// 동물 배열 생성
Animal* animals[2];
animals[0] = new Dog();
animals[1] = new Cat();
// 다형성 작동
animals[0]->makeSound(); // "Woof!" 출력
animals[1]->makeSound(); // "Meow!" 출력
// 메모리 해제
delete animals[0];
delete animals[1];
return 0;
}
animals[0]의 정적 타입은 Animal*
하지만 가리키는 객체의 실제 타입은 Dog입니다.
makeSound()가 virtual이므로 런타임에 Dog::makeSound() 호출
Animal 클래스에서 makeSound()를 virtual로 선언하지 않았다면
int main() {
// 동물 배열 생성
Animal* animals[2];
animals[0] = new Dog();
animals[1] = new Cat();
// 다형성 작동하지 않음
animals[0]->makeSound(); // "Animal sound!" 출력
animals[1]->makeSound(); // "Animal sound!" 출력
// 메모리 해제
delete animals[0];
delete animals[1];
return 0;
}
animals[0]의 정적 타입은 Animal*
virtual이 없으므로 컴파일 시점에 Animal::makeSound() 호출
런타임에 객체의 실제 타입(Dog)은 고려되지 않음
class AAnimal {
public:
virtual void makeSound() const = 0; // 순수 가상 함수
};
class Dog : public AAnimal {
public:
void makeSound() const { std::cout << "Woof!" << std::endl; }
};
int main() {
// AAnimal animal; // 오류: 추상 클래스는 인스턴스화 불가
AAnimal* animal = new Dog();
animal->makeSound(); // "Woof!" 출력
delete animal;
return 0;
}
순수 가상 함수(= 0으로 선언)가 있는 클래스는 추상 클래스된다.
추상 클래스는 인스턴스화할 수 없지만 포인터로 사용 가능
모든 순수 가상 함수를 구현한 파생 클래스는 인스턴스화할 수 있음
virtual 키워드로 가상 함수 선언
파생 클래스에서 오버라이딩 가능
포인터나 참조를 통해 기본 클래스 타입으로 접근해도 파생 클래스의 함수 호출
동적 할당된 멤버에 대해 새 메모리 할당
내용 복사 (단순히 포인터만 복사하지 않음)
메모리 누수 방지를 위한 기존 메모리 해제
순수 가상 함수(= 0으로 선언)를 하나 이상 포함
직접 인스턴스화 불가능
파생 클래스는 모든 순수 가상 함수를 구현해야 함
오버라이딩할 때 자식 클래스에서는 virtual 키워드를 반복해서 표시하지 않아도 된다. 부모 클래스에서 virtual로 선언된 함수는 자식 클래스에서도 자동으로 가상 함수로 취급된다.
예를 들어
class Animal {
public:
virtual void makeSound() { std::cout << "Animal sound" << std::endl; }
};
class Dog : public Animal {
public:
// virtual 키워드 생략 가능
void makeSound() { std::cout << "Woof!" << std::endl; }
};
위 코드에서 Dog::makeSound()는 virtual 키워드를 명시하지 않았지만, 부모 클래스의 makeSound()가 가상 함수이므로 자동으로 가상 함수가 된다.
다만 C++11부터는 가독성과 의도를 명확히 하기 위해 override 키워드를 사용하는 것이 좋음
class Dog : public Animal {
public:
void makeSound() override { std::cout << "Woof!" << std::endl; }
};
override 키워드를 사용하면
1. 코드 가독성 향상 (오버라이딩 의도가 명확해짐)
2. 컴파일러가 오버라이딩 오류를 잡아준다 (부모 클래스에 해당 가상 함수가 없는 경우 에러 발생)
요약하면, 오버라이딩 시 자식 클래스에서 virtual 키워드는 생략 가능하지만, 명확성을 위해 override 키워드 사용을 권장
부모 클래스에서 virtual 키워드를 사용하는게 좋다. 이 키워드의 유무에 따라 프로그램의 동작이 완전히 달라짐
virtual 키워드를 사용하지 않으면 정적 바인딩(static binding)이 일어난다.
class Animal {
public:
void makeSound() { std::cout << "Animal sound" << std::endl; } // virtual 없음
};
class Dog : public Animal {
public:
void makeSound() { std::cout << "Woof!" << std::endl; }
};
int main() {
Animal* pet = new Dog();
pet->makeSound(); // "Animal sound" 출력 (부모 클래스의 함수 호출)
delete pet;
return 0;
}
위 코드에서 pet->makeSound()는 Animal::makeSound()를 호출, 컴파일러는 포인터 타입(Animal*)만 보고 호출할 함수를 결정하기 때문이다.
virtual 키워드를 사용하면 동적 바인딩(dynamic binding)이 일어난다
class Animal {
public:
virtual void makeSound() { std::cout << "Animal sound" << std::endl; } // virtual 있음
};
class Dog : public Animal {
public:
void makeSound() { std::cout << "Woof!" << std::endl; }
};
int main() {
Animal* pet = new Dog();
pet->makeSound(); // "Woof!" 출력 (자식 클래스의 함수 호출)
delete pet;
return 0;
}
이 경우 pet->makeSound()는 Dog::makeSound()를 호출. 런타임에 객체의 실제 타입을 확인하여 적절한 함수를 호출하기 때문
부모 클래스에서 virtual 키워드는
1. 다형성을 활용하기 위해 반드시 필요
2. 생략하면 자식 클래스의 오버라이딩 함수가 무시될 수 있다
3. 특히 기본 클래스 포인터로 자식 클래스 객체를 다룰 때 중요하다
애니멀, 도그, 캣 예제에서는 동적 바인딩을 통해 각 동물 타입에 맞는 소리를 내기 위해 virtual 키워드는 필수적!