프로그래밍 공부
작성일
2023. 5. 18. 22:19
작성자
WDmil
728x90

C++에서의 다형성인 오버로딩, 오버라이딩 중 오버 라이딩을 사용하는 부모자식 클레스에 대한 내용이다.

 

C++ 에서는 부모자식 클래스에 대한 함수 오버라이딩과 privet을 제외한 함수 공유가 가능하다.

 

이는 부모클래스와 자식클래스로 이어지는 것을 이야기한다. 예를들어 책이라는 묶음에 책내용이라는 자식이 있는 격 이다.

 

부모클래스와 자식클래스의 관계에서는 부모클래스는 기본적인 특성과 동작을 정의하고, 자식클래스는 이러한 특성과 동작을 상속받아 추가적인 기능을 추가하거나 변경할 수 있다. ( 변경은 함수 오버라이딩을 의미한다 ) 이를 통해 코드의 확장성과 유연성을 증가시킬 수 있다.

 

다음 코드는 기본적인 부모자식클래스의 예시이다.

코드예시는 다음과 같다.

class Mother
{
private:
	int a;
	// 상속되어도 private은 물려받아 사용할 수 없게된다.
public:

	auto GetA() const
	{
		cout << "Mother" << endl;
		return a;
	}
	void SetA(const int& a) { this->a = a; }
protected:
	int b;
	int c;
	// 여기서 만들어지는 애들은 자식과 내 클레스에서만 사용할 수 있다.
};

class Chiled : public Mother
{
	int b;
	// 자식과 부모가 겹친다면, 자식의 것이 먼저 우선시된다.
public:
	auto GetA() const
	{
		c;
		b; // chiled
		Mother::b; // Mother

		cout << "chiled" << endl;
		return Mother::GetA();
	}
};
int main()
{
	Chiled c;

	c.SetA(1);
	c.GetA();
	c.Mother::GetA();
	// Mohter이 생성되지 않아도 Mother의 함수를 사용할 수 있다.
	return 0;
}

위 코드에서의 설명은. 자식클래스와 부모클래스에 대한 기본적인 사용방법을 이야기한다.

 

private는 물려받아 사용할 수 없으나, 그 이외에 protected나 public은 물려받아 자식클래스에서 사용이 가능하다.


위에서 설명했듯, private는 물려받아 사용할 수 없으나, 그 이외에 protected나 public은 물려받아 자식클래스에서 사용이 가능하다. 그러나, 이러한 자식클래스 선언에서 부모클래스의 다른값들을 public이나 private으로 설정해주는 방법이 존재한다.

 

이는, 부모클래스의 값을 이어받아온 자식클래스가. 사용할 때는 자신이 설정한 값대로 변경하여 사용하나. 그곳에서 한번 더 자식클래스를 만들 경우. 그 자식클래스 에서는 변경한 형태 대로 따라오게 취급한다.

 

다음은. 그러한 권한변경에 대한 코드 예시이다.

#include <iostream>

using namespace std;


/*
상속 접근 지정자      부모 클래스         자식클래스
   public         public            public
                  protected         protected
                  private            접근 불가

   protected      public            protected
                  protected         protected
                  private            접근 불가

   private        public            private
                  protected         private
                  private            접근 불가
*/

class Base
{
private:
    int privateDate;

protected:
    int protectedData;

public:
    int publicData;
    void Test() {}
};

class Derived : private Base
{
public:
    Derived()
    {
        Test();
        publicData;
        protectedData;
        // privateDate;
    }
};

class A : public Derived
{
public:
    A()
    {
        // Test();
        // pulbicData;
        // protectedData;
        // 접근하는것들을 이미 private으로 받았기 때문에 다시 상속시키면, 이미 private이기 때문에 접근할 수 없게된다.
    }
};

int main()
{
    A a;
    //a. // 아무것도 뜨지 않는다.

	return 0;
}

위 코드에서 볼 수 있듯. Derived에서 가져온 Base의 변수들에 따라 Derived에서는 Base의 변수를 사용할 수 있지만,

class A에서는 Derived에서 이미 Base의 값들을 전부 private으로 선언하였기 때문에, 사용할 수 없게된다.

 


부모자식클래스 에서는 Up casting과 Down casting이 존재한다.

  • UP casting
    • 일반적으로 자식클래스가 더 크다고 생각할 수 있지만, 개념적으로 부모클래스 의 것을 물려받기 때문에, 부모클래스가 더 상위라고 볼 수 있다. 이는 약간 폴더 같은 개념이라고 이해하면 된다.
    • 예를들어, 어떤 폴더에 게임파일이 존재한다고 해보자. 이때 게임파일은 폴더에 들어가기 전에는 접근할 수 있는 방법이 존재하지 않을것 이다. 폴더를 열어야만 게임파일에 들어갈 수 있기 때문에, 이는 폴더가 게임파일보다 위에 있는. 접근할때 폴더를 먼저 열어야하는. 폴더가 게임의 상위이다. 라고 할 수 있을것이다.
    • 이때, 자식 -> 부모 쪽으로 이동하기위해 포인터 작업을 진행하는것 을 Up casting 이라고 한다.
  • Down casting
    • 부모클래스의 포인터가 가리키는 객체를 자식클래스의 포인터로 가리키게 하는것이다.
    • 부모클래스의 포인터가 부모의 객체를 가리킬 때 에는 자식클래스의 값을 가리킬 수 없게된다.
    • 이는. 위에서 설명한 UP casting과는 정 반대되는 개념이라고 할 수 있다.
#include <iostream>

using namespace std;
class Snack {};

class Chitos : public Snack {};
class ChocoChip : public Snack {};
class Homerunball : public Snack {};

int main()
{
	Snack* chocochip = new ChocoChip(); // 업케스팅(Up casting)
	Snack* chitos = new Chitos();

	Snack* sneck[2] = { chitos, chocochip }; // 업케스팅을 사용해서 배열로 사용이 가능하다. 접근이 가능하다. 관련이 있는것 끼리 묶어야한다.

	Chitos* test = static_cast<Chitos*>(chitos); // 다운케스팅 많이 사용하지는 않는다.

	// Chitos* cc = new Snack(); // error이 된다. 아래에서 위로만 케스팅이 된다.

	return 0;
}

위와같이 배치하였을 때, UPcasting을 활용해서 과자종류 에 대한 배열을 유지하고 생성해줄 수 있게된다.


이때, 부모클래스와 자식클래스 간의 생성과 종료에 대한 순서를 살펴보자.

 

일반적으로, 부모클래스가 생성되고 나서 자식클래스가 생성되는 순서로 class가 생성된다.

 

이는, 자식클래스는 부모클래스가 생성되지 않으면 생성되어 값을 유지할 수 없기 때문이다.

 

그래서 일반적인 생성시 순소는 부모 -> 자식순으로 유지된다.

 

그렇다면, 종료시 소멸자는 어떤식으로 동작하게 될까?

 

대부분의 코드동작방식은 후입선출 방식으로 이루어진다.

 

그렇기 때문에 생성과 종료는 다음과 같은 순서로 이어지게 된다.

 

부모[생성] -> 자식[생성] -> 자식[종료] -> 부모[종료]

다음 코드 예시를 통해 살펴보자.

#include <iostream>

using namespace std;

class Parent
{
	int a;
public:
	Parent()
		:a(10)
	{
		cout << "Base Constructor" << endl;
	}
};

class Child : public Parent
{
	double b;

public:
	Child()
		:/*Parent() , */b(10.0) // 부모클래스의 생성자가 먼저 호출 되고 자식클래스가 생성되면서, 자식클래스의 맴버 클래스 생성자가 만들어 지면서 만들어지게 된다.
	{
		cout << "Derved Constructor" << endl;
	}
};

class A			{ public: A() { cout << "A constructor" << endl; } };
class B : public A	{ public: B() { cout << "B constructor" << endl; } };
class C : public B	{ public: C() { cout << "C constructor" << endl; } };
class D : public C	{ public: D() { cout << "D constructor" << endl; } };

int main()
{
	Child child;


	D d;
	// D만 호출하더라도, 나머지의 class의 생성자가 전부 생성되어 나타나게 된다.
	return 0;
}

위와 같은 코드를 통해 생성자와 종료자를 살펴보자.

위와 같은 방식대로 실행될것 이다.

 

위코드 에서 는 생성방식 에 대해 설명한다. class가 생성될 때 에는 부모가 먼저 생성되고 그 이후에 자식이 생성되는 순서로 이어진다.


// 소멸자 관련 이야기

#include <iostream>

using namespace std;

class A
{
	int a;
	// 4byte
public:
	A() { cout << "A constructor" << endl; }
	~A() { cout << "A destructor" << endl; }
};

class B : public A
{
	double b;
	// 16byte
public:

	B() { cout << "B constructor" << endl; }
	~B() { cout << "B destructor" << endl; }
};

int main()
{
	B b;
	cout << sizeof(A) << endl;
	cout << sizeof(B) << endl;
	// 페딩바이트가 여러개 있을떄 cpu가 중복접근을 할 수 있기 때문에, 그걸 막기위해 페딩바이트를 추가해주어서 하는것.
	return 0;
}

위 코드에서 우리는 생성자와 소멸자의 순서에 대해 알 수 있다.

 

A가 먼저 생성되었으나, 종료될 때 에는 B가 먼저 종료되는것 을 알 수 있다.

728x90