오늘 배운 내용 중 중요한 부분인 상속을 좀 더 자세히 이해해 보고자, 구글링과 유튜브 영상을 통해 공부한 후 정리한 글이다.
1. 상속을 사용하는 이유
- 클래스 간의 관계 설정하여 코드 재사용성 높이기
- Class Interface Consistency : 일관적인 클래스 인터페이스 구성 가능
- abstract / interface
- pure virtual function
- Dynamic Function Binding : 동적 바인딩을 사용하기 (C++는 기본적으로 정적 바인딩)
- virtual function
- virtual table
2. 상속 기본 예제 및 접근 지정자
아주 간단한 상속 예제
- 상속 받은 클래스는 부모의 함수를 사용할 수 있다.(public 이니까)
#include <iostream>
using namespace std;
class Animal
{
public:
void sleep() const
{
cout << "I'm sleeping\n";
}
};
class Human : public Animal
{
public:
void driveCar() const
{
cout << "I'm driving\n";
}
};
int main()
{
Human me;
me.driveCar(); // I'm driving
me.sleep(); // I'm sleeping
return 0;
}
접근 권한 키워드 예제
- 우리가 Base클래스를 상속할 때 어떤 접근 권한 키워드로 가져올 것인가에 따라 클래스의 동작이 달라지게 된다.
- 정리하기에 앞서...
- 앞으로 이야기 할 내용들은 상속한 자식 클래스로 만들어진 인스턴스 입장에서 생각해야 한다. 아래 코드 예시로 들면, main 함수에서 사용할 때를 말하는 것이다.
- 상속받은 클래스 내에서의 동작은 모두 같다. (public과 protected 까지 접근 가능하다)
- 지금 예제는 한번의 상속만 있기에 크게 와닿지 않지만, 다중 상속을 하는 경우 차이점을 느낄 수 있다.
- Base 클래스
- 클래스 내부에서는 당연하게 모두 접근이 가능하다.
- 그렇기 때문에 setter함수를 public 함수로 만들어서 상속받은 클래스나 인스턴스에서 사용하도록 만든다.
- main함수에 생성된 인스턴스 입장에서는 public함수만 사용이 가능하다.
- 클래스 내부에서는 당연하게 모두 접근이 가능하다.
- public으로 상속
- 부모 클래스에 선언된 그대로 받아들이면 된다.
- 클래스 내부에서는 public과 protected에 접근이 가능할 것이고,
- main 함수에 클래스로 생성된 인스턴스 입장에서는 public만 접근이 가능할 것이다.
- 부모 클래스에 선언된 그대로 받아들이면 된다.
- protected로 상속
- public 이 protected로 바뀌게 된다.
- 클래스 내부에서의 동작은 같지만, main 함수에서 생성한 인스턴스는(derived2) public 함수인 setter 함수를 사용하지 못하게 된다.
- private로 상속
- public과 protected 모두 private로 바뀌게 된다.
- 현재 예시에서는 protected와 똑같이 동작한다.
- 아래의 예시 중 주석 표시가 된 코드는 오류를 발생시키는(엑세스 할 수 없음) 코드이다.
#include <iostream>
using namespace std;
class Base
{
private:
int pri;
protected:
int pro;
public:
void SetPri(int n)
{
pri = n;
}
void SetPro(int n)
{
pro = n;
}
};
class Derived1 : public Base
{
public:
void Test()
{
Base::pro = 100;
//Base::pri = 100;
}
};
//이 경우 public 이 protected로 보인다
class Derived2 : protected Base
{
void Test()
{
Base::pro = 100;
//Base::pri = 100;
}
};
//이 경우 public과 protected가 모두 private로 보인다.
class Derived3 : private Base
{
void Test()
{
Base::pro = 100;
//Base::pri = 100;
}
};
int main()
{
Base base;
base.SetPri(10);
base.SetPro(100);
//base.pro = 0;
//base.pri = 0;
Derived1 derived1;
derived1.SetPri(100);
derived1.SetPro(100);
Derived2 derived2;
//derived2.SetPri(100);
//derived2.SetPro(100);
Derived3 derived3;
//derived3.SetPri(100);
//derived3.SetPro(100);
}
3. 생성자 소멸자 및 가상 함수
1. 생성자 소멸자 호출 순서
- 기본적인 예제 (항상 부모의 생성자 먼저, 자식의 소멸자 먼저)
#include <iostream>
using namespace std;
class Animal
{
public:
Animal()
{
cout << "animal constructor\n";
}
~Animal()
{
cout << "animal destructor\n";
}
};
class Cat : public Animal
{
public:
Cat()
{
cout << "cat constructor\n";
}
~Cat()
{
cout << "cat destructor\n";
}
};
int main()
{
Cat cat; //1
//Animal* animalptr = new Animal(); //2
//delete animalptr;
//Cat* catptr = new Cat(); //3
//delete catptr;
}
- 주의할 사항
- 아래 코드를 실행시키면, cat 의 소멸자가 호출되지 않는 문제점이 존재한다.
- 그렇기 때문에 해결책으로
- base class의 소멸자를 virtual public으로 선언하기 (대부분의 경우)
- base class의 소멸자를 protected로 선언하기 (base class를 오브젝트로 만들지 않는 경우)
Animal* polyCat = new Cat();
2. virtual 을 이용한 Dynamic Polymorphism
- virtual function 만들기
- 부모 클래스에서 virtual 함수를 만든다면, 앞에 virtual 을 붙여주고,
- 해당 virtual 함수를 상속받았다면, override 키워드를 뒤에 붙여주도록 한다.
- 간단한 dynamic polymorphism 예제
- 아래와 같은 코드를 작성하면, 컴파일 타임이 아니라 런타임에 우리가 polyAnimal의 타입을 정해줄 수 있다.
- 1을 입력한 경우 meow를 2를 입력한 경우 animal이 출력될 것이다.
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void speak()
{
cout << "animal\n";
}
virtual ~Animal() = default;
};
class Cat : public Animal
{
public:
void speak() override
{
cout << "meow\n";
}
};
int main()
{
int i = 0;
cin >> i;
Animal* polyAnimal;
if (i == 1)
polyAnimal = new Cat();
else
polyAnimal = new Animal();
polyAnimal->speak();
delete polyAnimal;
}
4. Virtual Table
- 가상함수가 아닌 일반 함수로 선언된 경우
- Animal 클래스는 double변수 한개이므로 크기가 8 byte
- Cat 클래스는 Animal 클래스의 크기 + 자기가 가진 double 변수 8byte = 16byte
#include <iostream>
using namespace std;
class Animal
{
public:
void speak()
{
cout << "animal\n";
}
//virtual ~Animal() = default;
private:
double height;
};
class Cat : public Animal
{
public:
void speak()
{
cout << "meow\n";
}
private:
double weight;
};
int main()
{
cout << "Animal size: " << sizeof(Animal) << "\n"; //8(height - double) byte
cout << "Cat size: " << sizeof(Cat) << "\n"; //8(height) + 8(weight) byte
}
- speak 함수는 virtual로 선언한 경우
- 아래 그림 처럼 virtual table이 생성되기 때문에 64bit 컴퓨터에서 포인터 변수 한개의 크기(8byte)가 추가된다.
- 가상함수 테이블은 클래스마다 하나씩 가지고 있다 (개체마다 아님!)
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void speak()
{
cout << "animal\n";
}
//virtual ~Animal() = default;
private:
double height;
};
class Cat : public Animal
{
public:
void speak() override
{
cout << "meow\n";
}
private:
double weight;
};
int main()
{
cout << "Animal size: " << sizeof(Animal) << "\n"; //16 byte
cout << "Cat size: " << sizeof(Cat) << "\n"; //24 byte
}
'C++' 카테고리의 다른 글
C++ TIL day 9 (3) | 2024.12.27 |
---|---|
C++ TIL day 8 (0) | 2024.12.26 |
Smart Pointer 보충 (0) | 2024.12.24 |
C++ TIL day 7 (2) | 2024.12.24 |
C++ TIL day 6 (0) | 2024.12.23 |