프로그램/C,C++

[C++ 기본3] 5. 상속

naudhizb 2022. 5. 15. 15:43
반응형

상속은 객체지향언어에서 가장 중요한 개념중에 하나이다.
상속을 통하여 코드 재활용성의 가능성을 높일 수 있다.

C++에서는 상속을 위하여 클래스 뒤에 상속받는 클래스의 이름을 콜론(:)으로 분리하여 명시한다.

// 5_상속1.cpp   132page ~
#include <iostream>
#include <string>

class People
{
    std::string name;
    int    age;
};

class Student : public People  // 상속 ( inheritance )
{
    int    id;
};

class Professor : public People
{
    int    major;
};

int main()
{

}

private 지시어로 붙은 멤버 변수의 경우 파생 클래스에서도 접근이 불가능하다.

// 5_상속2

class Base  // base class, super class  , 기반 클래스
{
private:   // 자신의 멤버에서만 접근 가능
    int a;

protected: // 자신과 파생클래스에서 접근가능
    int b;

public:    // 어디서나 접근 가능.
    int c;
};

class Derived : public Base   // Derived class, subclass, 파생클래스
{
public:
    void foo()    {
        a = 0; // error. 기반 클래스의 private 멤버는
                // 파생클래스라도 접근할수 없다.
        c = 0; // ok
    }
};
int main()
{
    Base base;
    base.a = 0; // error
    base.c = 0; // ok
}

파생클래스를 사용하기 위해서는 기반클래스의 생성자 또한 호출되어야 한다. 하지만, 이 경우 컴파일러가 기반 클래스의 default 생성자를 호출하기 때문에 사용자가 다른 동작을 원하는 경우, 기반클래스의 디폴트 생성자가 정의되어 있지 않은 경우 사용자가 직접 기반클래스의 생성자를 호출해주어야 한다.

// 상속과 생성자.   135page ~
#include <iostream>

// 핵심
// 1. 파생 클래스의 초기화 리스트 목록에서 기반 클래스의 생성자를
//    호출하는 코드를 컴파일러가 생성해 주는것

// 2. 기반 클래스의 생성자는 기본적으로 default 생성자 호출
//    변경하려면 사용자가 초기화 리스트에서 기반 클래스생성자를
//      명시적으로 호출해야 한다.
// 3. 기반 클래스에 디폴트 생성자가 없으면 반드시 파생 클래스에서
//    명시적으로 호출해야 한다.

class Base
{
public:
//    Base()      { std::cout << "Base()"  << std::endl; }
    Base(int a) { std::cout << "Base(int)" << std::endl; }
    ~Base()     { std::cout << "~Base()" << std::endl; }
};
class Derived : public Base
{
public:
//    Derived()        { std::cout << "Derived()" << std::endl; } // error
    Derived() : Base(0) { std::cout << "Derived()" << std::endl; } // error

    Derived(int a) : Base(a) { std::cout << "Derived(int)" << std::endl; }
    ~Derived()     { std::cout << "~Derived()" << std::endl; }
};
int main()
{
    //Derived d;
    Derived d(1);
}

생성자를 protected로 설정하게 되면 해당 클래스를 추상 클래스로 만드는 효과가 있다. C++에서는 인터페이스를 만들 때에도 해당 방식을 사용할 수 있다.

// 139 page
// protected 생성자 : 자신의 객체는 생성할수 없지만(추상적 존재, abstract)
//        파생 클래스의 객체는 생성할수 있게(구체적 존재, concrete) 하는 기술
class Animal 
{
protected:
    Animal() {}
};
class Dog : public Animal
{
};

int main()
{
    // 다음중 에러를 모두 고르세요
    Animal a; // 1. error
    Dog    d; // 2. ok
}

파생클래스에서 기반클래스의 멤버로의 접근을 막기 위해서는 접근 변경자를 이용해서 권한을 변경할 수 있다. 단, 접근 권한의 확대는 불가능하다.

// 접근 변경자
class Base
{
private:   int a;
protected: int b;
public:    int c;
};
class Derived : private Base // 접근 변경자
{                            // 기반 클래스의 접근 권한을
};                            // 축소할때 사용. 확대는 안됨

int main()
{
    Base base;    base.c = 10; // ok
    Derived derv; derv.c = 10; // error
}

코드 wrapping을 수행할 때 유저에게 허용되지 않은 동작을 감추고자 할 때 유용하게 사용할 수 있습니다.


// 이미 list가 있습니다.
#include <list>

// 그런데, 사용자가 스택을 요구 합니다.
template<typename T> class stack : private std::list<T>
{
public:
    void push(const T& a) { std::list<T>::push_back(a); }
    void pop()            { std::list<T>::pop_back(); }
    T&   top()            { return std::list<T>::back(); }
};
int main()
{
    stack<int> s;
    s.push(10);
    //s.push_front(30); // error. private 입니다.
}
반응형