본문 바로가기

교육 노트/C++ 심화강의

[C++ 때려잡기] C++ 심화강의 14 객체지향 마지막 강의 가상함수, 정적 바인딩, 동적 바인딩

2018/08/27 - [교육 노트/C++ 심화강의] - [C++ 때려잡기] C++ 심화강의 1 객체 지향 프로그래밍과 클래스

2018/08/27 - [교육 노트/C++ 심화강의] - [C++ 때려잡기] C++ 심화강의 2 접근 한정자

2018/08/27 - [교육 노트/C++ 심화강의] - [C++ 때려잡기] C++ 심화강의 3 생성자와 소멸자

2018/08/29 - [교육 노트/C++ 심화강의] - [C++ 때려잡기] C++ 심화강의 4 복사 생성자와 깊은 복사

2018/08/29 - [교육 노트/C++ 심화강의] - [C++ 때려잡기] C++ 심화강의 5 this 포인터

2018/08/29 - [교육 노트/C++ 심화강의] - [C++ 때려잡기] C++ 심화강의 6 Class 선언과 정의

2018/08/29 - [교육 노트/C++ 심화강의] - [C++ 때려잡기] C++ 심화강의 7 inline 함수

2018/08/31 - [교육 노트/C++ 심화강의] - [C++ 때려잡기] C++ 심화강의 8 Friend function, friend class

2018/08/31 - [교육 노트/C++ 심화강의] - [C++ 때려잡기] C++ 심화강의 9 연산자 오버로딩, chaining

2018/08/31 - [교육 노트/C++ 심화강의] - [C++ 때려잡기] C++ 심화강의 10 클래스의 핵심 상속

2018/08/31 - [교육 노트/C++ 심화강의] - [C++ 때려잡기] C++ 심화강의 11 다중 상속과 가상 상속

2018/08/31 - [교육 노트/C++ 심화강의] - [C++ 때려잡기] C++ 심화강의 12 오버라이딩 (overriding)

2018/08/31 - [교육 노트/C++ 심화강의] - [C++ 때려잡기] C++ 심화강의 13 다형성, 다운 캐스팅 업 캐스팅


저번 시간 마지막에


1. 다이나믹 캐스팅을 사용할수 없어요..

2. 오버라이딩했는데 적용이 안되요..



출처: http://see-ro-e.tistory.com/entry/C-때려잡기-C-심화강의-13-다형성-다운-캐스팅-업-캐스팅 [SeeRoE 프로그래밍 기록]

1. 다이나믹 캐스팅을 사용할수 없어요..

2. 오버라이딩했는데 적용이 안되요..



출처: http://see-ro-e.tistory.com/entry/C-때려잡기-C-심화강의-13-다형성-다운-캐스팅-업-캐스팅 [SeeRoE 프로그래밍 기록]

1. 다이나믹 캐스팅을 사용할수 없어요..

2. 오버라이딩했는데 적용이 안되요..


라고 하고 끝났는데..


지금 까지 코드로 다이나믹 캐스팅을 하려고하면  다형 클래스이어야 한다고 에러가 나온다.

분명 다형성을 가진 클래스를 작성하였는데 ㅜㅜ


또 오버라이딩 했는데 적용이 안된다는것은...



저번에 이런식으로 오버라이딩 했다.


여기서


이렇게 weapon을 다운캐스팅 하지 않고 그상태로 attack을 호출하게 되면



어택의 부모가 불리게된다.


#include <iostream>
using namespace std;

class Weapon
{
public:
    void Attack() { cout << "무기로 공격!"; };

protected:
    int power;
};

class Knife
    : public Weapon
{
public:
    void Attack() { cout << "칼로 공격!"; };

};


int main()
{
    
    Weapon* weapon = new Knife();

    weapon->Attack();

    delete weapon;
    return 0;
}



이걸 해결하기 위해서 knife의 attack가 불리도록 downcasting을 해보자


    ((Knife*)weapon)->Attack();

다이나믹 캐스트는 지금 사용 불가능 하므로

일반 캐스팅을 했다.


잘나온다.


아 그러면 앞으로는 해당 무기가 무엇인지 언제나 파악하고 오버라이딩 함수를 쓸때마다 해당 무기에 맞게 다운캐스팅을 하여 함수를 호출해줘야하는것일까?

아니 그럼 에초에 오버라이딩은 왜있는거고

그것보다 나는 웨폰은 생성한 적도 없는데 웨폰의 어택이 불리는건 이상하지않은가?


왜 웨폰의 어택이 불리는것일까?




1. 정적 바인딩 과 동적 바인딩


바인딩은 연결하는것이다.


클래스에서 함수는 메모리에 한번 올라간다고 했다.

메모리에 weapon과 knife의 attack이 올라가있다.


함수는 메모리에 올라가고 함수 포인터를 통하여 해당 함수에 접근하는데

그 함수포인터가 요함수 저함수 가르키는도록 하는것이 바인딩이다.



그리고 기존의 함수들은 정적 바인딩이다

이제 다들 알겠지만 동적은 런타임 정적은 컴파일 타임이다.

즉 저 함수가 연결되는 시점은 정적이다.


내가 weapon*로 만드는순간 저 웨폰포인터를 통해 불리는 모든 어택 함수는 정적으로 이미

weapon 의 attack으로 정해져버렸다


그래서 knife로 만들어도 저 attack이 불리는것이다

knife는 동적할당이므로 만드는 시점은 동적이기 때문이다


그래서 저 바인딩 동작이 attack에서는 동적으로 동작하도록 변경해야한다



2. 동적 바인딩 (dynamic binding) 와 가상함수

해결 방안은 간단하다


함수명 앞에 virtual 이라는 키워드를 붙이면 된다.




virtual 키워드는 컴파일러에게 요놈은 내가 런타임시간에 어떤함수를쓸지 결정해서 쓸께~ 라고 알려주는 역할을 한다.



따라서 동적할당을 하면 또 동적으로 함수가 바인딩이 된다.


그리고 virtual 키워드를 붙였기 때문에 해당 함수를 가상함수라고한다.

그냥 명칭이 그렇다



이제 함수를 사용해보자


#include <iostream>
using namespace std;

class Weapon
{
public:
    virtual void Attack() { cout << "무기로 공격!"; };

protected:
    int power;
};

class Knife
    : public Weapon
{
public:
    void Attack() { cout << "칼로 공격!"; };


};


int main()
{
    
    Weapon* weapon = new Knife;

    weapon->Attack();

    delete weapon;
    return 0;
}

와 형변환 필요없이 잘나온다!

이제 신경쓸필요없이 사용할수있게되었다.


또한 이러한 가상함수가 1개라도 있어야

해당 함수가 다형클래스가 된다. 실제 다형으로 사용할수 있게 되기때문같은데

암튼 따라서 다이나믹 캐스팅을 사용할려면 가상함수가 1개 이상 존재해야한다.






2. 가상 소멸자


#include <iostream>
using namespace std;

class Weapon
{
public:
    virtual void Attack() { cout << "무기로 공격!"; };
    ~Weapon()
    {
        cout << "웨폰 지움" << endl;
    }
protected:
    int power;
};

class Knife
    : public Weapon
{
public:
    void Attack() { cout << "칼로 공격!"; };

    ~Knife()
    {
        cout << "나이프 지움" << endl;
    }
};


int main()
{
    
    Weapon* weapon = new Knife;

    delete weapon;
    return 0;
}



하면

소멸자가 호출될때를 출력해보면


delete에서


부모클래스만 지워진다 ㅡㅡ

이렇게 되면 나이프에서 처리해줘야 하는 부분은 호출되지 않는다.


그 이유를 우리는 이미 배웠다.

정적 바인딩때문에

소멸자가 weapon 소멸자에 바인딩 되었기 때문이다.


따라서

소멸자에도 virtual를 붙여주어야한다.


#include <iostream>
using namespace std;

class Weapon
{
public:
    virtual void Attack() { cout << "무기로 공격!"; };
    virtual ~Weapon()
    {
        cout << "웨폰 지움" << endl;
    }
protected:
    int power;
};

class Knife
    : public Weapon
{
public:
    void Attack() { cout << "칼로 공격!"; };

    ~Knife()
    {
        cout << "나이프 지움" << endl;
    }
};


int main()
{
    
    Weapon* weapon = new Knife;

    delete weapon;
    return 0;
}


잘 지워진다.






이제 진짜진짜 모든 기능을 다 구현 할수 있게 되었다.





3. 순수 가상함수

그런데  weapon이놈은 사실 상속을 위해서만 만들어놓은 놈이고

new weapon이나 Weapon w; 이런식으로 웨폰 객체를 만들일은 없다.

그런데도 불구하고 attack 함수를 구현할 필요는 없다.


그래서 내용을 지웠다.


#include <iostream>
using namespace std;

class Weapon
{
public:
    virtual void Attack() { };
    virtual ~Weapon()
    {
        cout << "웨폰 지움" << endl;
    }
protected:
    int power;
};

class Knife
    : public Weapon
{
public:
    void Attack() { cout << "칼로 공격!"; };

    ~Knife()
    {
        cout << "나이프 지움" << endl;
    }
};


int main()
{
    
    Weapon* weapon = new Knife;

    delete weapon;
    return 0;
}


이렇게 코딩을 하고 같이 개발하는 친구에게 나머지 무기 50개를 만들어달라고 하고 코드를 넘겨주었다

넘겨줄때

Weapon의 attack함수를 오버라이딩 해서 만들어~

라고 신신당부 하였다.


그런데 친구가 49번째 무기까지 잘 만들다가 50번째 무기에서 술먹고 들어와 attack함수를 오버라이딩 하지 않았다.

그런데 마침 해당 무기가 마지막에서나 얻을수 있는 최종 무기라 출시후에 보스몹을 잡으려던 사람들이 보스를 잡으려고 아무리 노력해봐도

공격이 되지않아 우리가 만든 게임을 전부 환불해주어야 했다 ㅜ


이런상황을 미연에 방지하기위하여

C++ 은 순수 가상 함수를 제공한다.



순수 가상함수는 가상함수에 구현 대신에 = 0; 이라고 작성하여

아무것도 구현하지 않은 상태를 말한다.

그런데 그러면  {}이랑 무슨 차이인가?


순수 가상 함수를 이용하는 이유는

개념적으로 상위 클래스로 존재하나
해당 클래스에서 구현할 필요는 없고
자식 클래스에서 구현이 반드시 필요한 경우


중요한 점은 반드시 필요하다는것이다.

반드시


만약 구현을 하지 않으면?


#include <iostream>
using namespace std;

class Weapon
{
public:
    virtual void Attack() = 0;
    virtual ~Weapon()
    {
        cout << "웨폰 지움" << endl;
    }
protected:
    int power;
};

class Knife
    : public Weapon
{
public:
    //void Attack() { cout << "칼로 공격!"; };

    ~Knife()
    {
        cout << "나이프 지움" << endl;
    }
};


int main()
{
    
    Weapon* weapon = new Knife;

    delete weapon;
    return 0;
}



안만들면 못쓴다.




순수 가상 함수를 가지고있는 클래스는 애초에 인스턴스화 (객체화) 가 불가능하다

그래서 이걸 클래스는 클래스인데 추상적이라 추상 클래스라 부르고

순수 가상 함수를 오버라이딩 하지 않으면 해당 클래스도 추상클래스가 되는것이다.


따라서 애초에 인스턴스화 가 불가능하여 에러 뿜뿜을 하게 된다.


친구와 같은 오류가 미연에 방지되는것이다.






이제 진짜 객체지향에 관한 문법을 전부 배웠다.


다음 실습을 진행해보자

1 도형의 넓이를 구해보자
기본 도형을 정의하는 부모클래스가 있다.
자식 클래스에는 직사각형, 정사각형, 원, 정삼각형, 직각 삼각형이있다.
각각의 도형의 넓이를 구하는 함수를 만들어보자!



2. 나만의 String을 만들어보자    (C++에서는 기본적으로 string클래스를 제공한다 소문자로하면 이름이 곂치므로 String으로 하자)

String은 + 를 하면 두 문자열이 더해짐

정수를 곱하면 해당하는 문자열을 정수만큼 반복함

At(int)함수는 해당하는 문자열의 문자를 가져온다.

Find(‘문자열’)함수는 해당 문자가 어디에 있는지 index로 알려준다. (중복되는게 있으면 가장 먼저 잇는 index반환 없으면 -1을 반환)








지금까지 심화강의가 C++ 의 OOP, class의 마지막 강의입니다.



사실 앞으로 만들어놓은 ppt강의자료는 열거형 과 공용체,예외처리,템플릿,템플릿 특수화 과 같은 개념도 많이 남았는데

이건 객체지향으로 묶기는 조금 이상하기도하고

글로 설명하기 너무 어려워서 나중에.. 시간이 나면 업로드 하도록 하겠습니다.


그외에도 문법을 넘어서 STL, 자료구조, 알고리즘... 다양하게 배울것은 많이 남았지만

해당 과정까지 다 이해했다면 이것은 지금처럼 차례 차례 공부하는것이 아니라 모르면 찾아보고 이해하고 코딩하고 하는 형식으로 해도 충분할것입니다,



신입생들을 상대로 ppt에 간략하게 적고 앞에서 말로 설명하던거라 글로 쓰기 많은 내용들을 그냥 생략해버려서 컴파일러, 링커, 메모리구조와 같은 깊은 내용을 죄다 생략했으면서 강의자료에는 당연히 아는듯양 써놓아 설명이 부실하기도하고 지금까지 했던거와 앞으로 할꺼 정리하려고 블로깅을 시작했다가 드라이브에 강의자료가 있는걸 발견하여 삘 받아서 작성한건데 동아리 교육이 빡센편이라 실제로 강의할때는 피피티 하나치가 블로그 글로 따지만 7,8개 나와버려서 결국 날림으로 작성하기도 했지만..

이걸로 차근차근 다 정독 해야지! 보다는(부실해요 ㅎㅎ) 흐름과 개념정도만 익히는 정도로 봐주셨으면 감사하겠습니다.


이상으로 강의를 마치도록 하겠습니다.




728x90