17.4 Constructors and initialization of derived classes

주홍영·2022년 3월 20일
0

Learncpp.com

목록 보기
165/199

https://www.learncpp.com/cpp-tutorial/constructors-and-initialization-of-derived-classes/

이번 섹션에서는 derived class의 initialization에서 constructor의 역할에 대해 자세하게 살펴본다

Initializing base class members

현재까지 배운 방법으로는 derived class에서 base class의 member variable을 초기화하거나 그러지 않았다. 우리가 instantiate하면 derived class의 constructor가 호출되는데 여기에 base class의 멤버 변수를 조작하지는 않았다

그렇다면 derived class에서 base class의 멤버변수를 초기화하면 되지않을까?

class Derived: public Base
{
public:
    double m_cost {};

    Derived(double cost=0.0, int id=0)
        // does not work
        : m_cost{ cost }
        , m_id{ id }
    {
    }

    double getCost() const { return m_cost; }
};

위와 같이 코드를 짜면 합리적으로 보인다.
하지만 c++에서 위와 같은 접근을 금지하고 있다
다른 말로 member variable의 value는 오직 해당 클래스의 member initializer list에서 설정될 수 있다는 것이다

왜 그렇게 c++은 허용하고 있는 것일까? 이는 const와 reference variable과 관련이 있다
만약 derived에서 수정하려는 base 클래스의 멤버변수가 const라고 하자. const variable은 생성과 동시에 초기화가 되어야 한다. 그리고 base class의 constructor가 이를 수행할 것이다
이후 derived class의 constructor에서 이를 다시 initialization을 하려고 하면 오류가 발생할 것이다. 따라서 이러한 접근을 금지하고 있는 것이다
즉, c++은 모든 variable이 한번만 initialize 될 수 있도록 보장하고 있다

그렇다면 다음과 같이 하면 어떻게 될까?

class Derived: public Base
{
public:
    double m_cost {};

    Derived(double cost=0.0, int id=0)
        : m_cost{ cost }
    {
        m_id = id;
    }

    double getCost() const { return m_cost; }
};

위처럼 body에서 assignment를 하는 것은 허용이 된다
다만 이는 const와 reference 가 아니기 때문에 가능한 것이다
(const와 reference의 경우 생성과 동시에 initialization이 되어야 하기 때문에)
또한 이는 매우 inefficient하다. 왜냐하면 중복으로 값을 설정하고 있는 모습이기 때문이다

그렇다면 우리는 어떻게 하면 적절한 방법으로 Base class의 멤버 변수를 초기화 시킬 수 있을까?

다행히도 c++은 우리에게 명시적으로 constructor를 선택할 수 있는 권한을 주고 있다
이를 하기 위해서는 간단히 member initalizer list에서 Base class의 constructor를 호출하면 된다

class Derived: public Base
{
public:
    double m_cost {};

    Derived(double cost=0.0, int id=0)
        : Base{ id } // Call Base(int) constructor with value id!
        , m_cost{ cost }
    {
    }

    double getCost() const { return m_cost; }
};

위와 같이 member initializer list에서 Base {id}라는 constructor를 호출하고 있다

Derived class가 instantiate되는 과정을 자세히 풀어쓰면 다음과 같다

  1. Memory for derived is allocated.
  2. The Derived(double, int) constructor is called, where cost = 1.3, and id = 5
  3. The compiler looks to see if we’ve asked for a particular Base class constructor. We have! So it calls Base(int) with id = 5.
  4. The base class constructor member initializer list sets m_id to 5
  5. The base class constructor body executes, which does nothing
  6. The base class constructor returns
  7. The derived class constructor member initializer list sets m_cost to 1.3
  8. The derived class constructor body executes, which does nothing
  9. The derived class constructor returns

참고로 Base constructor가 어디에 있든 member initializer list에 있을 때 가장 먼저 실행되니 순서는 신경쓰지 않아도 된다. 다만 이해하기 쉽게 가장 처음에 놔두는게 좋을 것 같다

Now we can make our members private

이제 base class member를 초기화하는 방법을 알았으니 멤버 변수를 private으로 변경해도 된다

빠르게 복습하자면 public 멤버는 어디서나 접근이 가능했다. private의 경우는 오직 동일 클래스의 멤버 function에서만 접근이 가능했다.
즉, derived class에서 base 클래스의 private member variable에 접근하는 것은 불가능하다
따라서 base class의 public한 access function을 사용해야할 것이다

#include <iostream>

class Base
{
private: // our member is now private
    int m_id {};

public:
    Base(int id=0)
        : m_id{ id }
    {
    }

    int getId() const { return m_id; }
};

class Derived: public Base
{
private: // our member is now private
    double m_cost;

public:
    Derived(double cost=0.0, int id=0)
        : Base{ id } // Call Base(int) constructor with value id!
        , m_cost{ cost }
    {
    }

    double getCost() const { return m_cost; }
};

int main()
{
    Derived derived{ 1.3, 5 }; // use Derived(double, int) constructor
    std::cout << "Id: " << derived.getId() << '\n';
    std::cout << "Cost: " << derived.getCost() << '\n';

    return 0;
}

위 코드에서 멤버 변수가 모두 private 하지만 constructor를 통해서 초기화를 진행하고
access function을 통해서 접근하므로 문제 없다
따라서 다음과 같은 출력이 나온다

Id: 5
Cost: 1.3

Inheritance chains

#include <iostream>

class A
{
public:
    A(int a)
    {
        std::cout << "A: " << a << '\n';
    }
};

class B: public A
{
public:
    B(int a, double b)
    : A{ a }
    {
        std::cout << "B: " << b << '\n';
    }
};

class C: public B
{
public:
    C(int a , double b , char c)
    : B{ a, b }
    {
        std::cout << "C: " << c << '\n';
    }
};

int main()
{
    C c{ 5, 4.3, 'R' };

    return 0;
}

위 코드를 보면 C클래스를 main 함수에서 instantiate하고 있다
각 constructor마다 member initializer list에서 parent class의 contructor를 호출하고 있음으로 출력은 다음과 같다

A: 5
B: 4.3
C: R

Destructors

derived class가 파괴되면 constructor의 역순으로 destructor가 호출된다

Summary

derived class를 생성할 때 derived class의 constructor는 어떤 base class의 constructor를 호출할지를 결정한다. 만약 명시하지 않는다면 default constructor를 호출한다. ㅁ

profile
청룡동거주민

0개의 댓글