반응형

C++ Inheritance (상속) _ 다형성, 업캐스팅, 다운캐스팅

 

다형성(Polymorphism)

  • 생물학에서 유래한 단어, 사전적 의미로 동일한 종에 속하는 생물이라 할지라도 다양한 변이를 가지는 현상을 말함, 즉 여러개의 형태를 갖는다는 것,
  • OOP의 특징중 하나

 

C++ 에서 다형성을 가능하게 해주는 요소들

  1. 상속 관계에서 상위 클래스 또는 하위 클래스로 형변환(up casting, down casting)
  2. 메소드 오버라이딩, 가상함수
  3. 순수 가상 함수
  4. 함수 오버로딩
  5. 연산자 오버로딩

 

상속 관계에서 상위 클래스 또는 하위 클래스로 형변환

  • Up casting : 상속관계에서  하위 클래스가 상위클래스로 형변환 하는 것
  • Down casting : 상속관계에서 업캐스팅했던 상위 클래스 인스턴스가 하위클래스로 형변환 하는 것 

 

Up casting 방법

상위클래스* 클래스이름 = new 하위클래스; 

 

이때 하위클래스 와 상위클래스의 접근 권한이 아래와 같이

 

class 하위클래스 : public 상위클래스

{

}

 

public 으로 되있어야 한다.

 

 

 

 

Down casting 방법

하위클래스* 클래스이름 = (하위클래스*)업캐스팅된상위클래스변수;

 

 

 

 

ex1)

동물

  ↑

고양이

실행결과

40번라인에 있는 animal은 Animal 이 아닌 Cat을 할당 받았고 sound()를 호출해보면 ??? 가 뜬다 즉 하위 클래스를 할당 했더라도, 상위클래스 처럼 쓸 수 있는것이다, 이렇게 상위 클래스의 자료형에 하위 클래스를 할당하는 것을 업캐스팅 이라고 한다,

 

41번 라인은 Down casting 이다, 상위 클래스처럼 쓰이고 있는 하위클래스를 본래의 자료형에 맞게 할당하는것 이때 상위 클래스에서 하위클래스로 형변환 할때는 명시적 형변환이 필요하다, 그뒤 sound()를 호출해보면 Cat::Sound() 함수 정의대로 Mero~ 를 호출 하는것을 볼 수 있다.

 

 

주의점

이러한 형변환 성질을 이용해 여러 객체들을 상속관계로 디자인하고 최상위 클래스들로 Upcasting 한뒤, 객체들을 한꺼번에 하나의 배열에 두고 관리 할 수도 있다, 즉 한번에 for문 하나로 delete 할수도 있게 된다, 그러나 Upcasting 상태에서 delete 하게 되면, 하위클래스의 소멸자는 호출되지 않는다. 

 

 

업캐스팅 한뒤 delete 하게되면 상위 클래스 소멸자만 호출된다.

위 문제를 해결 하기위해서 즉, 하위클래스의 소멸자까지 호출해주게 하기 위해서는 다시 다운캐스팅을 한뒤 delete 해야 한다.

 

 

위처럼, down casting을 하여, delete 해주면 되긴하다, 그러나 만약 상속계층이 cat에서 끝나지 않고 하위클래스가 더 존재한다면?, 그리고 그중에 본래의 하위클래스 형태를 추적하기 힘들다면? 아래와 같은 상황을 보자

 

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#include <iostream>
 
class Animal
{
public:
    Animal()
    {
        std::cout << "Animal 생성자" << std::endl;
    }
 
    ~Animal()
    {
        std::cout << "Animal 소멸자" << std::endl;
 
    }
};
 
class Cat :public Animal
{
 
 
public:
    Cat()
    {
        std::cout << "Cat 생성자" << std::endl;
    }
 
    ~Cat()
    {
        std::cout << "Cat 소멸자" << std::endl;
    }
};
 
 
class SuperCat :public Cat
{
 
 
public:
    SuperCat()
    {
        std::cout << "SuperCat 생성자" << std::endl;
    }
 
    ~SuperCat()
    {
        std::cout << "SuperCat 소멸자" << std::endl;
    }
};
 
class MegaCat :public SuperCat
{
 
 
public:
    MegaCat()
    {
        std::cout << "MegaCat 생성자" << std::endl;
    }
 
    ~MegaCat()
    {
        std::cout << "MegaCat 소멸자" << std::endl;
    }
};
 
 
class SpecialCat :public MegaCat
{
 
 
public:
    SpecialCat()
    {
        std::cout << "SpecialCat 생성자" << std::endl;
    }
 
    ~SpecialCat()
    {
        std::cout << "SpecialCat 소멸자" << std::endl;
    }
};
 
 
 
int main()
{
    Animal* superCat = new SuperCat;
    std::cout << std::endl;
    Animal* superCat_2 = new SuperCat;
    std::cout << std::endl;
    Animal* megaCat = new MegaCat;
    std::cout << std::endl;
    Animal* cat = new Cat;
    std::cout << std::endl;
 
    Animal* animalArr[4= { superCat,superCat_2,megaCat,cat };
 
    for (int i = 0; i < 4; i++)
    {
        delete animalArr[i];
    }
 
 
 
    return 0;
}
cs

실행 결과

예상대로 상위클래스 소멸자만 호출됐다

특히나 위와같이 배열에 담고서 관리할 경우 원래 형태가 어떤 것이었는지 추적하기 어렵다, 이때 한가지 방법이 존재하는데, 가상함수 라는 것이다, 가상함수는 따로 다시 정리하겠지만 여기서는 최상위 클래스의 소멸자에 virtual 이라는 키워드만 붙여주면 된다

 

virtual 키워드는 가상함수를 정의할 때 쓰인다

오직 최상위 클래스 소멸자 앞에 virtual 만 붙였을 뿐인데, 아래와 같은 결과가 나온다

정상적으로 소멸자가 모두 호출된것을 볼 수 있다.

 

즉 최상위 클래스의 소멸자는 가상함수로 만드는게 좋다

 

 

 

정리

  • 상위클래스와 하위클래스간에는 형변환이 가능한데, 이를 Up Casting, Down Casting 이라한다
  • Up Casting 하면 하위 클래스를 상위 클래스처럼 사용 가능하다
  • 어떤 객체들을 최상위 클래스로 Upcasting 하면 하나의 배열로 관리하기 용이하다 그러나 최상위 클래스의 소멸자는 가상함수로 만들어 소멸자가 원활하게 호출되게 해야한다.

 

반응형
반응형

C# Inheritance(상속)

대상:

클래스

 

목적:

클래스의 재사용-> 코드량 줄이기

 

상속 클래스의 역할

부모클래스 : 상속을 하는 클래스

(parent class, base class, 상위 클래스)

 

자식 클래스 : 상속을 받는 클래스

(child class, derived class, 파생 클래스)

 

 

 

 

 

 

상속 관계 표시 형식

class Parent

{

}

 

class Child : Parent

{

}

Parent class

  

Child class

 

상속 관계 표시는 화살표로 하며, 화살 정방향이 부모를 가리키고, 역방향은 자식을 향한다

 

 

 

 

 

 

 

상위 클래스 접근 지정자

class Parent

{

private //Child 에서 접근 불가

protected //Child 접근 가능

public //Child Main 같은 외부에서도 접근 가능

}

 

class Child : Parent

{

}

 

 

 

ex1)

 

using System;

 

namespace Inheritance

{

    class A

    {

        private void PrintPrivate()

        {

            Console.WriteLine("private");

        }

 

        protected void PrintProtected()

        {

            Console.WriteLine("protected");

        }

 

        public void PrintPublic()

        {

            Console.WriteLine("public");

        }

    }

 

    class B : A

    {

        public void Print()

        {

            //PrintPrivate(); //접근 불가능

            PrintProtected();

            PrintPublic();

        }

    }

 

 

    class Program

    {

        static void Main(string[] args)

        {

            B Test = new B();

            Test.Print();

            Test.PrintPublic();

        }

    }

}

 

 

 

 

상속 관계에서 생성자, 소멸자 호출 순서

  • 생성자 호출 : 상위 -> 하위

 

  • 소멸자 호출 : 하위 -> 상위

 

 

 

ex2)

 

using System;

 

namespace Inheritance

{

    class A

    {

        public A()

        {

            Console.WriteLine("A 생성자");

        }

        ~A()

        {

            Console.WriteLine("A 소멸자");

 

        }

 

        private void PrintPrivate()

        {

            Console.WriteLine("private");

        }

    }

 

    class B : A

    {

        public B()

        {

            Console.WriteLine("B 생성자");

        }

 

        ~B()

        {

            Console.WriteLine("B 소멸자");

 

        }

    }

 

    class Program

    {

        static void Main(string[] args)

        {

            B test = new B();

        }

    }

}

 

코드 실행 결과

생성자는 Base생성자 -> Derived 생성자 순, 소멸자는 Derieved 소멸자 -> Base 소멸자 순이다 

 

 

생성자 호출 순서

 

 

소멸자 호출 순서

 

반응형
반응형

C++ Inheritance(상속)

  • 어떤 클래스가 다른 클래스와 기반(base) 파생(derived) 관계를 가질때, 파생클래스 에서는 기반클래스의 필드(멤버변수)   메소드(멤버함수) 들을 사용할 있다.
  • 하지만 기반 클래스의 private 변수,함수 들은, 파생클래스에서도 접근이 불가능하다, protected, public 가능하다, 반면에 friend 키워드를 쓰면 가능하긴하다(friend 상속에 관계없이, 모든 변수, 함수에 접근할 있도록 접근 허용 권한을 주는것)

 

 

 

상속 관계 짓는 방법

클래스를 디자인할때, 콜론(:) 넣고 뒤에 접근제어자와 다른 클래스의 이름 붙이면 된다

 

ex1)

 

파생클래스 : 외부접근권한 기반클래스

class MyClassB : public MyClassA

 

 

이때 외부 접근권한을 public, private 정해 있는데

public 경우 클래스 외부에서도 기반 클래스의 public 변수, 함수들에 접근할 있게 되고

private 경우 파생클래스만 기반 클래스의 public 변수 함수들에 접근할 있다

 

 

ex2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
class CParent
{
public:
    CParent()
        :m_iA(0),m_iB(0),m_iC(0)
    {
        std::cout << "CParent 생성자" << std::endl;
    }
    
    ~CParent()
    {
        std::cout << "CParent 소멸자" << std::endl;
    }
public:
    int        m_iA;
 
protected:
    int        m_iB;
 
private:
    int        m_iC;
 
public:
    void Output()
    {
        std::cout << "A : " << m_iA << std::endl;
        std::cout << "B : " << m_iB << std::endl;
        std::cout << "C : " << m_iC << std::endl;
    }
};
 
class CChild : public CParent
{
public:
    CChild()
        :m_iD(0)
    {
        m_iB = 200;
        // m_iC는 private 이기 때문에 자식 내부에서도 접근이 불가능 하다
        //m_iC = 300; // error
        std::cout << "CChild 생성자" << std::endl;
    }
 
    ~CChild()
    {
        std::cout << "CChild 소멸자" << std::endl;
    }
 
protected:
    int        m_iD;
};
 
class  CChild1 : private CParent
{
public:
    CChild1()
        :m_iD(0)
    {
        m_iA = 100;
        m_iB = 200;
        std::cout << "CChild1 생성자" << std::endl;
 
    }
 
    ~CChild1()
    {
        std::cout << "CChild1 소멸자" << std::endl;
    }
 
private:
    int        m_iD;
};
 
class CChildChild : public CChild
{
public:
    CChildChild()
        :m_iE(0)
    {
        m_iD = 500;
        std::cout << "CChildChild 생성자" << std::endl;
    }
 
    ~CChildChild()
    {
        std::cout << "CChildChild 소멸자" << std::endl;
 
    }
 
private:
    int        m_iE;
};
 
 
int main(void)
{
    /*
    상속 관계에서의 생성자 호출 순서 : 부모 -> 자식 순으로 호출됨
    상속관계에서의 소멸자 호출 순서 : 자식 -> 부모 순으로 호출됨
    */
    CParent            parent;
    std::cout << std::endl;
    CChild            child;
    std::cout << std::endl;
    CChild1            child1;
    std::cout << std::endl;
    CChildChild        childchild;
    std::cout << std::endl;
 
    parent.m_iA = 100;
 
    child.m_iA = 200;
 
    // CChild1 클래스는 CParent를 private 상속을 하고 있으므로 CParent에 
    // Public으로 설정되어있는 멤버들도 외부에서는 접근이 불가능하다.
    //child1.m_iA = 300;
    //child1.Output();
 
 
    return 0;
}
cs

실행화면

 

정리

  1. 상속관계에서 파생 클래스는 기반 클래스의 생성자를 먼저 호출한뒤 그 다음에 파생 클래스의 생성자를 호출한다 
  2. 소멸자는 생성자의 역순으로 호출된다, 즉 파생클래스 소멸자 -> 기반 클래스 소멸자 순으로 호출 된다
  3. 파생클래스 : public 기반클래스 -> 외부에서도 기반 클래스의 public 변수및 함수 접근 가능,                             
  4. 파생클래스 : private 기반클래스 -> 외부에서 기반클래스의 모든 변수 및 함수 접근 불가능
  5. protected 접근 제어자는 파생클래스가 기반클래스의 변수 또는 함수에 접근할 수 있도록 해준다
반응형

+ Recent posts