C++ Inheritance

LeemHyungJun·2022년 9월 17일
0

C++ Study

목록 보기
4/12
post-thumbnail

예시 코드)
https://github.com/GbLeem/CPP_Study/tree/main/NoCode
30.cpp ~ 38.cpp

Inheritance Intro

  • 상속을 이용하는 이유
    • class relationship
    • code reuse
    • class interface consistency
      • abstract
      • interface
      • pure function
      • virtual function
    • dynamic function binding
      • virtual function
      • virtual table
  • 상속의 종류에 대한 내용
class A
{
public:
	int x;
protected:
	int y;
private:
	int z;
};

class B : public A
{
	// x is public
	// y is protected
	// z is not accessible from B
};

class C : protected A
{
	// x is protected
	// y is protected
	// z is not accessible from C
};

class D : private A    // 'private' is default for classes
{
	// x is private
	// y is private
	// z is not accessible from D
};

Virtual Function

  • Base class 의 destructor는 무조건 virtual public으로 (혹은 protected) 선언
#include<iostream>

class Animal
{
public:
	Animal()
	{
		std::cout << "animal constructor" << std::endl;
	}
	virtual ~Animal() //virtual function
	{
		std::cout << "animal destructor" << std::endl;
	}
};

class Cat : public Animal
{
public:
	Cat()
	{
		std::cout << "cat constructor" << std::endl;
	}
	~Cat()
	{
		std::cout << "cat destructor" << std::endl;
	}
};

int main()
{
	Animal* polyCat = new Cat(); 
	delete polyCat; //base 클래스의 소멸자를 virtual로 만들지 않았을 때 cat destructor가 호출되지 않음

	return 0;
}
  • Dynamic polymorphism :
    상속과 virtual을 통해 runtime 도중에 생성할 오브젝트를 결정
#include<array>
#include<iostream>

class Animal
{
public:
	virtual void speak()
	{
		std::cout << "Animal" << std::endl;
	}
	virtual ~Animal() = default;
};

class Cat : public Animal
{
public:
	void speak() override
	{
		std::cout << "Cat" << std::endl;
	}
};

class Dog :public Animal
{
	void speak() override
	{
		std::cout << "Dog" << std::endl;
	}
};
 
int main()
{
	std::array<Animal*, 5> animals;

	for (auto& animalPtr : animals)
	 {
		int i = 0;
		std::cin >> i;
		if (i == 1)						
			animalPtr = new Cat();
		else
			animalPtr = new Dog();
	}

	for (auto& animalPtr : animals)
	{
		animalPtr->speak();
		delete animalPtr;
	}

	return 0;
}

Virtual Table

  • Virtual table :
    virtual과 override를 사용하면 virtal table을 가리키는 포인터 변수(8byte)를 가지게 되어 오브젝트의 사이즈가 원래보다 커지게 된다.
#include<array>
#include<iostream>

class Animal
{
public:
	virtual void speak()
	{
		std::cout << "Animal" << std::endl;
	}
private:
	double height; //8byte
};

class Cat : public Animal
{
public:
	void speak() override
	{
		std::cout << "Cat" << std::endl;
	}
private:
	double weight; //8+8 byte
};

int main()
{
	std::cout << sizeof(Animal) << std::endl; //16 byte
    std::cout << sizeof(Cat) << std::endl; //24 byte

	Animal* polyAnimal = new Cat();
	polyAnimal->speak(); //Cat 출력
	delete polyAnimal;

	return 0;
}
  • virtual table의 구조와 main 함수에서의 동작 그림

Pure Virtual Function

  • Pure Virtual Function : = 0 으로 표현된 것
    ex) virtual void speak() = 0;
    -> pure virtual function은 derived class에서 무조건 override 해야한다.
  • Abstract class :
    pure virtal function이 하나라도 있는 클래스
    -> 오브젝트 생성이 불가능하다
#include<iostream>

class Animal //abstract class
{
public:
	virtual void speak() = 0; //pure virtal function
	virtual ~Animal() = default;
private:
	double height;
};

class Cat : public Animal
{
public:
	void speak() override //override 필수!
	{
		std::cout << "Cat" << std::endl;
	}
private:
	double weight;
};

int main()
{
	Animal abs; //error (abstract class는 오브젝트 생성 x)
	Cat kitty;

	return 0;
}
  • Interface class :
    Base 클래스를 만들고 Derived 클래스가 추가될 때 유지 보수 측면에서 유용한 방식
    -> 내부에 implementation (모든 function을 pure virtual function)
    -> member variable 없애기

Multiple Inheritance

#include<iostream>

class Lion 
{
public:
	Lion()
	{
		std::cout << "Lion constructor" << std::endl;
	}
	virtual ~Lion()
	{
		std::cout << "Lion destructor" << std::endl;
	}

	virtual void speak()
	{
		std::cout << "Lion" << std::endl;
	}
private:
	double LionData;
};

class Tiger
{
public:
	Tiger()
	{
		std::cout << "Tiger constructor" << std::endl;
	}
	virtual ~Tiger()
	{
		std::cout << "Tiger destructor" << std::endl;
	}

	virtual void speak()
	{
		std::cout << "Tiger" << std::endl;
	}
private:
	double TigerData;
};

class Liger : public Lion, public Tiger
{
public:
	Liger()
	{
		std::cout << "Liger constructor" << std::endl;
	}
	~Liger()
	{
		std::cout << "Liger destructor" << std::endl;
	}
	void speak()override
	{
		std::cout << "Liger" << std::endl;
	}
private:
	double LigerData;
};

int main()
{
	std::cout<< sizeof(Liger) << std::endl; // 40
	Lion* polylion = new Liger();
    polylion->speak(); //Liger 
    delete polylion;
    return 0;
}
  • main 함수의 작동 방식과 구조

  • Diamond Inheritance 문제
    • 위 코드에서 가장 상위의 Animal class를 추가하는 경우 Diamond 형태의 상속 구조가 생성된다.
    • liger 오브젝트를 만드는 경우 Animal의 생성자가 두 번 호출되는 문제가 발생한다.
    • 상속을 할 때 virtual 키워드를 붙여(class Tiger : virtual public Animal) 해당 문제를 해결할 수 있다.

Virtual Inheritance

#include<iostream>

class Animal
{
public:
	virtual void speak()
	{
		std::cout << "Animal" << std::endl;
	}
private:
	double animalData;
};

class Lion :virtual public Animal
{
public:
	virtual void speak()
	{
		std::cout << "Lion" << std::endl;
	}
private:
	double lionData;
};

int main()
{
	std::cout << sizeof(Lion) << std::endl; //32

	Animal* polyAnimal = new Lion();
	polyAnimal->speak();
	delete polyAnimal;

	return 0;
}
  • virtual 상속 아닌 경우
    • Animal class : *VT, animalData
    • Lion class : *VT, animalData, lionData => 24byte
  • virtual 상속인 경우
    • Animal class : *VT, animalData
    • Lion class : *VT(Lion), lionData, *VT(Animal), animalData => 32byte
    • virtual table 구조에 offset이 있어서 derived class의 시작점을 알려준다.(*VT(animal)을 기준으로 Lion이 얼만큼 떨어져 있는지를 알려준다)
      -> offset이 있는 이유는 동적으로 생성된 derived 클래스의 크기를 나타내기 위해서이다
      -> 다이아몬드 구조의 경우 데이터의 중복을 막기 위해서 virtual inheritance를 사용한다
  • virtual 상속의 구조와 main함수의 동작

Dynamic Cast

  • UpCast :
    -> Derived 쪽에서 Base 쪽으로의 캐스팅
    -> 정보가 많은 쪽을 정보가 적은 쪽으로 옮겨주는 것이므로 문제 없는 방식
    ex1) Animal* animalPtr = catPtr;
    ex2) Animal* animalPtr = static_cast<Animal*> (catPtr);
    -> animalPtr은 Cat 클래스 안에만 있는(Animal 클래스에는 없는) 함수나 변수에는 접근 불가능
  • DownCast :
    -> Up cast의 반대
    -> 문제가 생길 가능성 크다
    ex)
    Animal* animalDownPtr = new Cat();
    Cat* catPtr = static_cast<Cat*>(animalDownPtr);
    -> catPtr이 Cat 클래스 안에만 있는 함수에 접근이 가능해진다.
  • dynamic cast를 통해서 cast 하려는 타입과 포인터의 타입이 같지 않으면 nullptr 반환하여 좀 더 안전한 코드를 만들수 있다.
    -> dynamic cast를 사용하지 않아도 문제 없는 구조가 잘 만든 class 구조이다..
  • dynamic cast를 사용하여 안정성을 높인 코드
class Animal
{...};
class Cat : public Animal
{...};
class Dog : public Animal
{...};

int main()
{
	Animal* animalPtr = new Animal();
	
	Cat* catptr = dynamic_cast<Cat*>(animalPtr); //dynamic cast
	
	if (catptr == nullptr)
	{
		std::cout << "not a cat object" << std::endl;
		return 0;
	}
	catptr->speak();
	catptr->knead();

	return 0;
}

Object Slicing

  • Object Slicing :
    Derived 클래스에서 Base클래스로 copy가 되는 경우 Derived 클래스 만의 정보가 잘려나가는 현상
  • Object Slicing 을 해결하는 방안
    1) copy constructor/assignment를 사용하지 못하게 막기
    ex)
    Animal& operator=(Animal other) = delete;
    Animal(const Animal& other) = delete;
    -> 이 방법은 Derived 클래스 간의 copy constructor도 막아버린다.
    2) copy constructor/assignment를 protected에 선언하기
    ex)
    protected:
    	Animal(const Animal& other) = default; 
    3) Base 클래스를 "pure abstract class" 로 만들어서 문제 발생의 여지를 막기
    -> 가장 좋은 방법
  • 상속의 문제
    • object slicing :
      위에서 언급한 해결방안으로 해결한다.
    • operator overloading :
      derived class에서 operator overloading을 전부 재정의 한다.

=> 가장 좋은 해결 방안은 base class를 pure abstract class로 만드는 것

I/O Inheritance

0개의 댓글