상속 (Inheritance)
-
클래스를 구현할 때, 다른 클래스나 객체에 구현되어 있는 동작을 유지하기 위해 그것을 기반으로 만드는 것.
-
일반적으로는 기반이 되는 것을 부모(Parent) 클래스, 기반을 활용하여 만드는 클래스를 자식(Child) 클래스라고 함.
-
상속을 하는 이유
-
상속의 종류
-
단일 상속(Single Inheritance): 하위 클래스에서 한 개의 상위 클래스로부터 상속 받는 것.
-
다중 상속(Multiple Inheritance): 하위 클래스에서 두 개 이상의 상위 클래스로부터 상속 받는 것.
-
다 단계 상속(Multilevel Inheritance): 하위 클래스에서 다른 하위 클래스로부터 상속 받는 것.
// Parent class
class FirstParent { }
class SecondParent { }
// Single Inheritance
class FirstChild : public FirstParent { }
class SecondChild : public SecondParent { }
// Multiple Inheritance
class ThirdChild : public FirstParent, pulbic SecondParent { }
// Multilevel Inheritance
class FirstDescendant : public FirstChild { }
class SecondDescendant : public SecondChild { }
// Multiple + Multilevel Inheritance
class ThirdDescendant : public FirstChild, public SecondChild { }
-
상속 관계에서 하위 클래스는 상위 클래스의 멤버 변수들을 모두 갖기 때문에, 하위 클래스의 객체를 생성할 때 하위 클래스와 상위 클래스의 생성자가 모두 호출되어야 하며, 객체가 파괴될 때 상위 클래스와 하위 클래스의 소멸자가 모두 호출되어야 함.
-
하위 클래스들은 자신의 고유한 멤버 변수나 함수 뿐만 아니라 상위 클래스의 멤버 변수와 함수까지 가질 수 있음.
#include <iostream>
using namespace std;
class Person {
private:
string name;
protected:
int age;
public:
Person(string _name, int _age) : name(_name), age(_age) {
cout << "Person Constructor." << endl;
}
Person(const Person& src) : name(src.name), age(src.age) { }
~Person() { cout << "Person Destructor." << endl; }
void introduce() { cout << name << ": Hi!" << endl; }
void setName(string _name) { name = _name; }
string getName() const { return name; }
void setAge(int _age) { age = _age; }
int getAge() const { return age; }
};
class Programmer : public Person {
private:
int numOfLang;
public:
Programmer(string _name, int _age, int _numOfLang)
: Person(_name, _age), numOfLang(_numOfLang) {
cout << "Programmer Constructor." << endl;
}
~Programmer() { cout << "Programmer Destructor." << endl; }
void setNumOfLang(int _numOfLang) { numOfLang = _numOfLang; }
int getNumOfLang() const { return numOfLang; }
};
class Designer : public Person {
private:
int numOfTools;
public:
Designer(string _name, int _age, int _numOfTools)
: Person(_name, _age), numOfTools(_numOfTools) {
cout << "Designer Constructor." << endl;
}
~Designer() { cout << "Designer Destructor." << endl; }
void setNumOfTools(int _numOfTools) { numOfTools = _numOfTools; }
int getNumOfTools() const { return numOfTools; }
};
int main() {
Programmer pr("ke2ek", 24, 4);
Designer dg("2ekke2", 40, 3);
pr.introduce();
cout << "I'm " << pr.getAge() << " years old." << endl;
dg.introduce();
cout << "I'm " << dg.getAge() << " years old." << endl;
return 0;
}
[결과]
더보기
Person Constructor.
Programmer Constructor.
Person Constructor. <---------- 중복 호출, virtual keyword 사용 시 한 번만 호출 가능
Designer Constructor.
ke2ek: Hi!
I'm 24 years old.
2ekke2: Hi!
I'm 40 years old.
Designer Destructor.
Person Destructor.
Programmer Destructor.
Person Destructor. <---------- 중복 호출, virtual keyword 사용 시 한 번만 호출 가능
-
상속의 접근 범위
-
클래스의 멤버 변수나 함수에 접근 제한자를 두는 것처럼, 상속 관계 안에도 접근 제한자를 통해, 하위 클래스에서 상위 클래스의 멤버 변수의 접근 범위를 정할 수 있음.
-
상속 관계 안에서의 접근 제한자에 따라 3가지의 상속 관계로 분류.
-
public 상속: 하위 클래스에서는 상위 클래스의 접근 제한자의 특성을 그대로 물려 받음.
-
protected 상속: 하위 클래스에서는 상위 클래스의 public 접근 제한자도 protected로 바꿔서 물려 받음. 따라서 외부에서는 하위 클래스의 객체로 상위 클래스의 public 멤버 함수를 호출할 수 없음.
-
private 상속: 하위 클래스에서는 상위 클래스의 모든 접근 제한자를 private으로 바꿔서 물려 받음. 따라서 하위 클래스 내부에서도 상위 클래스의 protected나 public 멤버 변수 및 함수를 사용할 수 없음.
함수 재정의 (Function Overriding)
-
상속 관계일 때, 상위 클래스의 함수를 하위 클래스에서 재정의 하는 것을 의미.
-
상위 클래스의 함수 이름, 반환형, 파라미터의 형태가 같은 함수를 동일하게 하위 클래스에서 정의.
#include <iostream>
using namespace std;
class Person {
public:
Person() { cout << "Person Constructor." << endl; }
~Person() { cout << "Person Destructor." << endl; }
void doWork() { cout << "Working ..." << endl; }
};
class Programmer : public Person {
public:
Programmer() : Person() {
cout << "Programmer Constructor." << endl;
}
~Programmer() {
cout << "Programmer Destructor." << endl;
}
void doWork() { cout << "Programming ..." << endl; }
};
int main() {
Person p;
p.doWork(); // Working ...
Programmer pr;
pr.doWork(); // Programming ...
return 0;
}
Up/Down Casting
// the same as above
int main() {
Programmer pr;
Person p = pr;
p.doWork(); // Working ...
// better than previous
Programmer pro;
Person &pRef = pro; // reference
pRef.doWork(); // Working ...
Person *pPtr = new Programmer; // pointer
pPtr->doWork();; // Working ...
// BUT, Data loss occurred still.
// We need virtual keyword!
return 0;
}
// the same as above
int main() {
/* Error
Person p;
Programmer pr = p;
pr.doWork();
*/
Person p;
Programmer *pr = reinterpret_cast<Programmer*>(&p);
pr->doWork();
return 0;
}
가상 함수 (Virtual Function)
-
부모 클래스의 멤버 함수 앞에 virtual 이라는 키워드를 붙이고, 자식 클래스에서 그 함수를 오버라이딩(Overidding)할 때, 포인터 또는 참조자 타입의 업 캐스팅(Up-casting)하여 객체를 사용하는 경우 자식 클래스의 함수를 호출함.
-
업 캐스팅 시 virtual 키워드를 붙여주지 않으면 부모 클래스의 함수가 호출 되는데, 그 이유는 함수의 호출 위치를 컴파일 타임(early time)에 결정하기 때문.
-
virtual 키워드를 붙여 주면, 해당 함수의 호출 위치가 코드가 컴파일 되는 시점에 결정되는 것이 아니라 런타임(late time)에 결정되기 때문에 하위 클래스의 오버라이딩 된 함수를 호출하게 됨.
#include <iostream>
using namespace std;
class Person {
public:
Person() { cout << "Person Constructor." << endl; }
~Person() { cout << "Person Destructor." << endl; }
virtual void doWork() { cout << "Working ..." << endl; }
};
class Programmer : public Person {
public:
Programmer() : Person() { cout << "Programmer Constructor." << endl; }
~Programmer() { cout << "Programmer Destructor." << endl; }
void doWork() { cout << "Programming ..." << endl; }
};
int main() {
Programmer pro;
Person &pRef = pro; // reference
pRef.doWork(); // Programming ...
Person *pPtr = new Programmer; // pointer
pPtr->doWork(); // Programming ...
return 0;
}
[결과]
더보기
Person Constructor.
Programmer Constructor.
Programming ... <----- 재정의 된 함수 호출
Person Constructor.
Programmer Constructor.
Programming ... <----- 재정의 된 함수 호출
Programmer Destructor. <----- 레퍼런스 변수에 의해 호출된 소멸자
Person Destructor. <----- 레퍼런스 변수에 의해 호출된 소멸자
#include <iostream>
using namespace std;
class Person {
public:
Person() { cout << "Person Constructor." << endl; }
~Person() { cout << "Person Destructor." << endl; }
virtual void doWork() { cout << "Working ..." << endl; }
};
class Programmer : public Person {
public:
Programmer() : Person() { cout << "Programmer Constructor." << endl; }
~Programmer() { cout << "Programmer Destructor." << endl; }
void doWork() { cout << "Programming ..." << endl; }
};
int main() {
Person *pPtr = new Programmer;
pPtr->doWork(); // Programming ...
return 0;
}
[결과]
더보기
Person Constructor.
Programmer Constructor.
Programming ...
#include <iostream>
using namespace std;
class Person {
public:
Person() { cout << "Person Constructor." << endl; }
~Person() { cout << "Person Destructor." << endl; }
virtual void doWork() { cout << "Working ..." << endl; }
};
class Programmer : public Person {
public:
Programmer() : Person() { cout << "Programmer Constructor." << endl; }
~Programmer() { cout << "Programmer Destructor." << endl; }
void doWork() { cout << "Programming ..." << endl; }
};
int main() {
Person *pPtr = new Programmer;
pPtr->doWork(); // Programming ...
delete pPtr;
return 0;
}
[결과]
더보기
Person Constructor.
Programmer Constructor.
Programming ...
Person Destructor.
#include <iostream>
using namespace std;
class Person {
public:
Person() { cout << "Person Constructor." << endl; }
virtual ~Person() { cout << "Person Destructor." << endl; }
virtual void doWork() { cout << "Working ..." << endl; }
};
class Programmer : public Person {
public:
Programmer() : Person() { cout << "Programmer Constructor." << endl; }
~Programmer() { cout << "Programmer Destructor." << endl; }
void doWork() { cout << "Programming ..." << endl; }
};
int main() {
Person *pPtr = new Programmer; // pointer
pPtr->doWork(); // Programming ...
delete pPtr;
return 0;
}
[결과]
더보기
Person Constructor.
Programmer Constructor.
Programming ...
Programmer Destructor.
Person Destructor.
추상 클래스 (Abstract Class)
-
순수 가상 함수(Pure Virtual Function)
-
추상 클래스란 순수 가상 함수를 하나 이상 포함하는 클래스를 의미
-
추상 클래스는 객체화(인스턴스화)를 할 수 없으며, 시도하게 될 경우 컴파일 오류가 발생함.
-
상위 클래스에 대한 객체 생성 요구가 발생하지 않는 상황이라면 상위 클래스에 순수 가상 함수를 선언하여 추상 클래스로 만드는 것이 권장됨.
#include <iostream>
using namespace std;
class Person {
public:
Person() { cout << "Person Constructor." << endl; }
virtual ~Person() { cout << "Person Destructor." << endl; }
virtual void doWork() = 0;
};
class Programmer : public Person {
public:
Programmer() : Person() { cout << "Programmer Constructor." << endl; }
~Programmer() { cout << "Programmer Destructor." << endl; }
void doWork() { cout << "Programming ..." << endl; }
};
int main() {
// Error
// Person p;
Person *pPtr = new Programmer;
pPtr->doWork(); // Programming ...
delete pPtr;
return 0;
}
가상 상속 (Virtual Inheritance)
#include <iostream>
using namespace std;
class Person {
public:
Person() { cout << "Person Constructor." << endl; }
virtual ~Person() { cout << "Person Destructor." << endl; }
void doWork() { cout << "Working ..." << endl; }
};
class Programmer : public Person {
public:
Programmer() : Person() { cout << "Programmer Constructor." << endl; }
virtual ~Programmer() { cout << "Programmer Destructor." << endl; }
};
class Designer : public Person {
public:
Designer() : Person() { cout << "Designer Constructor." << endl; }
virtual ~Designer() { cout << "Designer Destructor." << endl; }
};
class Freelancer : public Programmer, public Designer {
public:
Freelancer() : Programmer(), Designer() {
cout << "Freelancer Constructor." << endl;
}
~Freelancer() { cout << "Freelancer Destructor." << endl; }
};
int main() {
Freelancer f;
f.doWork();
return 0;
}
[결과]
// the same as above
class Person {
public:
Person() { cout << "Person Constructor." << endl; }
virtual ~Person() { cout << "Person Destructor." << endl; }
virtual void doWork() = 0;
};
class Programmer : public Person {
public:
Programmer() : Person() { cout << "Programmer Constructor." << endl; }
virtual ~Programmer() { cout << "Programmer Destructor." << endl; }
void doWork() { cout << "Programming ..." << endl; }
};
class Designer : public Person {
public:
Designer() : Person() { cout << "Designer Constructor." << endl; }
virtual ~Designer() { cout << "Designer Destructor." << endl; }
void doWork() { cout << "Designing ..." << endl; }
};
// the same as above
[결과]
// the same as above
int main() {
Freelancer f;
//f.doWork();
return 0;
}
[결과]
더보기
Person Constructor.
Programmer Constructor.
Person Constructor.
Designer Constructor.
Freelancer Constructor.
Freelancer Destructor.
Designer Destructor.
Person Destructor.
Programmer Destructor.
Person Destructor.
#include <iostream>
using namespace std;
class Person {
public:
Person() { cout << "Person Constructor." << endl; }
virtual ~Person() { cout << "Person Destructor." << endl; }
void doWork() { cout << "Working ..." << endl; }
};
class Programmer : public virtual Person {
public:
Programmer() : Person() { cout << "Programmer Constructor." << endl; }
virtual ~Programmer() { cout << "Programmer Destructor." << endl; }
};
class Designer : public virtual Person {
public:
Designer() : Person() { cout << "Designer Constructor." << endl; }
virtual ~Designer() { cout << "Designer Destructor." << endl; }
};
class Freelancer : public Programmer, public Designer {
public:
Freelancer() : Programmer(), Designer() {
cout << "Freelancer Constructor." << endl;
}
~Freelancer() { cout << "Freelancer Destructor." << endl; }
};
int main() {
Freelancer f;
f.doWork();
return 0;
}
[결과]
더보기
Person Constructor.
Programmer Constructor.
Designer Constructor.
Freelancer Constructor.
Working ...
Freelancer Destructor.
Designer Destructor.
Programmer Destructor.
Person Destructor.
#include <iostream>
using namespace std;
class Person {
public:
Person() { cout << "Person Constructor." << endl; }
virtual ~Person() { cout << "Person Destructor." << endl; }
virtual void doWork() = 0;
};
class Programmer : public virtual Person {
public:
Programmer() : Person() { cout << "Programmer Constructor." << endl; }
virtual ~Programmer() { cout << "Programmer Destructor." << endl; }
void doWork() { cout << "Programming ..." << endl; }
};
class Designer : public virtual Person {
public:
Designer() : Person() { cout << "Designer Constructor." << endl; }
virtual ~Designer() { cout << "Designer Destructor." << endl; }
void doWork() { cout << "Designing ..." << endl; }
};
class Freelancer : public Programmer, public Designer {
public:
Freelancer() : Programmer(), Designer() {
cout << "Freelancer Constructor." << endl;
}
~Freelancer() { cout << "Freelancer Destructor." << endl; }
// You must implement duplicate function overidding again.
void doWork() { cout << "Working too much ..." << endl; }
};
int main() {
Freelancer f;
f.doWork();
return 0;
}
[결과]
더보기
Person Constructor.
Programmer Constructor.
Designer Constructor.
Freelancer Constructor.
Working too much ...
Freelancer Destructor.
Designer Destructor.
Programmer Destructor.
Person Destructor.
다형성 (Polymorphism)
-
하나의 모습으로 다양한 형태를 가질 수 있는 성질.
-
클래스를 상속받아 사용하는 가장 큰 이유.
-
Person *pPtr = new Programmer; 또는 Person *pPtr = new Designer; 등 하나의 클래스 타입으로 다양한 형태를 가질 수 있다.
-
특히, 함수의 매개변수로 클래스 타입을 받을 때, 여러 하위 클래스를 처리해야 한다면 다형성이 꼭 필요하다.