프로그래밍 지식/C++

C++문법 / 상속, Inheritance - 2

게임이 더 좋아 2021. 12. 14. 16:24
반응형
728x170

앞 글에서 보았듯이 부모의 속성, 함수를 물려받는 것이 정말 유용하게 쓸 수 있다는 것을 알았다.

그리고 이왕 상속시킬 부모클래스를 만들것이라면

재사용성이 높은 클래스를 설계하고 만들어야겠다는 생각이 들면 좋다.

 

그러나 상속과 비슷한 것이 있었으니

바로 포함, Containment이다.

 

포함이란 객체를 멤버로 선언하여 해당 클래스의 기능을 재활용하는 방법이다.

클래스의 멤버는 타입에 제한이 없어서 기본형뿐만 아니라 객체도 포함이 가능하다.

클래스끼리 중첩되는 형식인데 구조체가 다른 구조체를 멤버로 가질 수 있는 것과 같다.

 

예를 통해서 알아보자

class Date
{
protected:
    int year, month, day;
public:
    Date(int y, int m, int d){year=y, month=m, day=d;}
    void OutDate(){printf("%d%d%d", year, month, day);}
    
};


class Product
{
private:
    char name[64];
    char company[32];
    Date validto;
    int price;
public:
    Product(const char *aname, const char* acompany, int y, int m, int d, int aprice): validto(y,m,d){
        strcpy(name, aname);
        strcpy(company, acompany);
        price = aprice;
    }
    
    void OutProduct(){
        // ..모든 멤버 출력
        ...
        validto.OutDate();
        ...
    }
    
};

 

위의 예시를 보면 Product 클래스가 Data 클래스 타입의 객체를 멤버로 가지고 있는 것을 알 수 있다.

다시 말해서 Product 클래스는 상속을 받지 않고도 Date 클래스의 멤버를 가지고 있는 것이다.

 

이것을 HAS A 관계라고 하는데

우리가 흔히 말하는 상속은 IS A 관계이다.

절대적으로 정해진 것은 아니지만

 

 클래스 간의 관계가 IS A 관계라면 상속을 사용하고

HAS A 관계라면 위와 같이 포함 기법으로 해당 클래스의 객체를 가지는 방법을 사용한다.

**해당 객체가 여러 개가 필요하다면 포함기법으로 쓰는 편이다.

 


 

그리고 private 상속이라는 것이 있다.

 

클래스는 객체의 액세스 속성을 결정하는데

위에서 Product가 validto를 public으로 할 지 private 할 지 정할 수 있는 것과 마찬가지다.

다시 말해서 포함을 사용할 때 포함하는 클래스가 포함되는 객체의 공개 여부를 정할 수 있다는 것이다.

 

상속의 경우에서는 자식 클래스가 상속받은 멤버의 액세스 지정을 변경할 수 있었다.

상속 액세스 지정자를 써서 가능했다.

 

여기서 말하는 private 상속은 부모의 멤버를 상속받으면서 private으로 바꾸어버리는 것을 말한다.

자식 클래스 자신은 이 멤버를 액세스할 수 있지만

외부나 여기서 파생되는 다른 클래스는 더 이상 참조가 불가능하다. 

private 상속은 포함과 유사한 효과가 나타나며 HAS A 관계를 구현하는 다른 방법이기도 하다.

 

결국 포함 기법과 private으로 상속하는 방법은 HAS A 관계를 표현한다는 면에서 유사하면서도

차이점도 있다.

가장 큰 차이점은 여러 개의 객체를 동시에 활용할 수 있는가의 여부다.

Product에서 유효기간 뿐만 아니라 제조일자와 같은 것이 포함된다면 그저 Product 클래스의 멤버로 Date를 하나 더 만들어주면 된다.

하지만 Private 상속의 경우는 같은 클래스를 두 번 상속할 수 없고 설사 가능하더라도

상속 받은 부모 클래스의 멤버이름과 같아 복수개의 정보를 동시에 표현할 수 없다.

 

다시 말해서 해당 클래스를 재사용할 것이면서 해당 클래스의 객체를 여러 개 사용할 것이다?

-> 포함

 

그렇지 않다.

-> 상속

 

위와 같이 간단히 나눌 수 있다.

 

또한 protected 멤버에 대한 접근이 포함과 private 상속과 다른데

만약 포함한 클래스라면 포함된 객체 입장에서는 외부이므로 public으로 선언된 멤버만 참조가 가능하다.

포함보다는 private 상속이 그래도 "상속"이라는 단어가 붙어서 더욱 긴밀한 관계를 갖고 있다고 보면 된다.

그래서 private 상속은 그래도 파생 클래스가 되어서 protected한 멤버에 접근이 가능하다.

 


 

또한 인터페이스 상속이라는 것이 있는데

 

priavte, public 상속과 포함에 대해 차이점을 생각해보자.

포함이나 private의 경우 인터페이스는 상속받지 않고 객체의 기능만 재사용할 뿐이다.

-> 구현 상속

 

Date 객체를 포함하는 Product 객체는 OutDate 함수를 호출하여 날짜를 출력할 수 있지만 자신이 이 함수를 가진 것은 아니어서 외부에서 호출할 수는 없다.

 

priavte 상속도 모든 멤버들을 상속받지만 자동으로 멤버들은 private으로 바뀌기 떄문에 외부에서는 역시 접근할 수 없다.

 

즉, 포함하는 클래스나, 상속받은 클래스에서만 사용가능하지 외부에는 공개된 인터페이스를 가지지 않는다.

 

다시 말하면 해당 클래스에서나 상속받거나 포함한 클래스의 멤버들을 쓸 수 있지

밖에서는 접근이 불가능하다는 말이다.

-> 해당 클래스에서 함수를 구현하거나 그런 것을 할 때나 쓸 수 있지

밖에서 해당 클래스의 상속 또는 포함된 객체의 멤버를 접근할 수는 없다는 말이다.

 

그렇다면 public 상속은??

그렇다. 해당 클래스에 대한 멤버를 외부에서도 호출이 가능하다. 

그래서 우리는 클래스를 재사용할 때 기능을 빌려쓰기만 할 것인지, 인터페이스까지 받을지 결정해야 한다.

또 이것이 HAS A, IS A 를 구분하는 기준이 되기도 한다.

 

단순히 기능만 필요하다면 private 또는 containment를 쓰면될 것이고

부모가 할 수 있는 모든 것들을 물려주고 싶다면 public 상속을 쓰면 될 것이다.

물론 이것도 절대적인 기준은 아니고 일반적으로 그렇다는 말이다.

 


 

클래스는 주로 멤버 변수와 멤버 함수로 구성되지만 타입도 포함될 수 있다.

클래스 내부적으로만 사용되고 외부에 알릴 필요가 없는 클래스가 있다면

선언문 안에서 다른 클래스를 중첩하여 선언한다.

 

?? 무슨말이지..?

예를 봐야 더 잘 알 수 있다.

class Product
{
private:
    char name[64];
    char company[32];
    int price;
    
    
    //??? 클래스 안에서 클래스를 만든다고?
    class Date
    {
    protected:
        int year, month, day;
    public:
        Date(int y, int m, int d){year=y, month=m, day=d;}
        void OutDate(){printf("%d%d%d", year, month, day);}
    };
    
    Date validto; //게다가 해당 타입의 멤버도 선언함.
    
public:
    Product(const char *aname, const char* acompany, int y, int m, int d, int aprice): validto(y,m,d){
        strcpy(name, aname);
        strcpy(company, acompany);
        price = aprice;
    }
    
    void OutProduct(){
        // ..모든 멤버 출력
        ...
        validto.OutDate();
        ...
    }

 

 

??클래스 안에서 클래스를 생성한 다음 해당 클래스 타입으로 멤버를 만들었네?

그렇다.

이렇게 쓰는 경우는

Date 클래스 자체가 일회성이라

외부에서는 필요치 않고 Product 클래스 안에서만 사용될 때 이렇게 사용한다.

 

그렇다면 이렇게 얻는 이점이 무엇이냐???

 

우포함 기법과 유사하면서도 객체만 포함하는 것이 아니라 클래스 선언문 자체가 포함되어있는 차이점이 있고

클래스의 동작을 도와주는 클래스가 필요하다면 외부에 둘 필요가 없이 선언문을 위와 같이 중첩시킨다.

그렇게 된다면 Product 클래스 자체는 혼자서도 모든 기능을 들고 있는 것이 되어 

외부와의 의존성은 낮아지며 재사용성이 증가하는 경향이 보인다.

 

물론 해당 클래스 안에서만 선언된 Date 이므로 밖에서 Date를 쓸 수는 없다는 단점이 있지만

정말 Product에서만 쓸 것이라면 이렇게 구현해도 좋은 방법이라고 할 수 있다.

 

정말 만약 쓰고 싶다면 Product에 먼저 접근한 다음 쓰는 것이 가능하다.

Product::Date now(11,22,33);

 

반응형
그리드형