C++ 상속 질문. Operator와 생성자가 상속되진 않지만 자식 클래스로부터 포함되는 이유는 무엇인가요?

조회수 3086회

공부 중에 강의노트에 Overloaded assignment operator not inherited – But can be invoked from derived class Constructors are not inherited– Are invoked from derived class’s constructor 라고 나와있는데요.

번역을 해보자면 오버로드된 연산자는 상속되지 않지만 자식 클래스로부터 포함될 수 있고(?? 이건 무슨 뜻인가요?) 생성자는 포함되지 않지만 자식 클래스 생성자로부터 포함되있다는건데.. 그 이유는 무엇인가요? 상속될거면 다 상속되지 헷갈리게 굳이 이렇게 될 이유가 있나요?

  • (•́ ✖ •̀)
    알 수 없는 사용자

1 답변

  • 해석을 조금 다르게 하셨네요.

    번역을 다시 해보면,

    Overloaded assignment operator not inherited – But can be invoked from derived class

    (오버로드된 대입 연산자는 상속되지 않는다. - 하지만 파생 클래스로부터 호출될 수 있다.)

    Constructors are not inherited – Are invoked from derived class’s constructor

    (생성자들은 상속되지 않는다. - 파생 클래스의 생성자로부터 호출된다.)


    이 부분을 정확히 이해하려면.. 코드로 설명해야겠네요.

    class A
    {
    public:
        A();     // 기본 생성자
        ~A();    // 소멸자
    
        A(const A& other);            // 복사 생성자
        A& operator=(const A& other); // 복사 대입 연산자
    
        A(A&& other);                 // 이동 생성자
        A& operator=(A&& other);      // 이동 대입 연산자
    };
    

    위 코드에 쓰여진 함수들은 전부 컴파일러가 자동으로 만들어주는 함수들인데요. 따라서 추가적인 기능을 구현하고 싶을 때에만, 우리가 직접 선언하고 정의하면 됩니다.

    보통 기본 생성자와 소멸자는 많이 쓰지만, 복사 생성자, 복사 대입 연산자 등은 기초적인 단계에선 알필요는 없지요.

    일단 대입 연산자는 생성자와 거의 동일하다고 보면 됩니다.


    여튼 중요한 점은 왜 생성자들은 상속을 쓰지 못하냐 하는 점이죠.

    상속된 함수가 어떻게 쓰이냐를 생각해보면, 답이 나옵니다. (혹시나 해서 말씀드리지만, 생성자도 함수입니다.)

    class A
    {
        virtual void Func();
    };
    
    class B : public A
    {
        virtual void Func() override;
    };
    
    int main()
    {
        A* ptr1 = new A();
        A* ptr2 = new B();
    
        ptr1->Func(); // class A의 함수 호출
        ptr2->Func(); // class B의 함수 호출
    }
    

    보통 함수를 상속하는 경우 위와 같이 쓰이겠죠? 즉, 실제 생성된 클래스형을 찾아서 해당되는 함수만을 호출하기 위한게 가상함수인데요.

    만약 생성자도 가상함수로 만들어버리면, 자식 클래스를 생성할 때, 자식의 생성자만 호출되고, 부모의 생성자는 호출되지 않겠죠.

    그런데, 생성자의 역할은 보통 멤버변수의 초기화인데, 자식 클래스에서 부모 클래스의 멤버변수까지 다 초기화해주는 코드를 매번 작성하려면 골치가 아플 뿐더러, 객체지향의 원칙에도 어긋나게 됩니다.

    즉, 각 클래스는 자신의 멤버변수를 자신의 생성자에서 초기화하는게 합리적이죠.

    따라서, 생성자는 가상함수를 쓰는게 아니라, 자식 클래스의 생성자에서 부모 클래스의 생성자를 따로 호출(invoke)해주는게 맞는거죠.

    하지만 우리가 직접 매번 호출하는 코드를 작성하려면 실수로 빼먹을 수도 있고, 귀찮기도 하니까, 컴파일러가 알아서 호출하는 코드를 생성해줍니다.

    class A
    {
    public:
        A()    // 생성자
        {
            a = 0; // 멤버 변수 초기화
        }
    private:
        int a;
    };
    
    class B : public A
    {
    public:
        B()    // 생성자
        {
            // A(); // 이 코드가 암시적으로 생겨난다고 생각하시면 될 듯.
    
            // 실제로는 생성자 내부에 이렇게 생성되는건 아니고,
            // 생성자 초기화 리스트에 추가될 겁니다.
            // B() : A() { b = 0; }  이런 형태로.
    
            b = 0;
        }
    private:
        int b;
    };
    

    즉, B 클래스를 생성할 때, A클래스의 생성자가 먼저 호출되고, 그 다음에 B클래스의 생성자가 호출된다고 생각하시면 됩니다.

    따라서, 이를 통해 파생 클래스에서는 부모 클래스의 멤버변수 초기화를 따로 고려하지 않아도 되게 되는 거지요.

    대입 연산자도 같은 맥락으로 이해하시면 될 것 같습니다.

    혹시나 대입 연산자가 뭔지 잘 모르실 수 있으니 간단히 설명드리면,

    class Object {};
    
    int main()
    {
        Object a;        // 기본 생성자가 호출됩니다.
        Object b = a;    // 복사 생성자가 호출됩니다. (a의 값을 복사하여 b 인스턴스를 생성)
        Object c;
        c = a;           // 복사 대입 연산자가 호출됩니다. (c는 이미 생성된 인스턴스이기 때문)
        return 0;
    }
    
    • 해석은 invoke가 호출의 의미가 맞는 것 같네요! 제가 해석도 틀리게 했네요 ㅋㅋ 자세하게 답변 해주셔서 너무 감사드립니다! 저장해뒀다가 나중에 기억 안 날때 다시 봐도 크게 도움 될 듯 합니다. 알 수 없는 사용자 2016.10.1 10:48

답변을 하려면 로그인이 필요합니다.

프로그래머스 커뮤니티는 개발자들을 위한 Q&A 서비스입니다. 로그인해야 답변을 작성하실 수 있습니다.

(ಠ_ಠ)
(ಠ‿ಠ)