소년코딩

생성자 멤버 초기화 리스트 (Constructor member initializer list)

이전 포스트 생성자에서 대입 연산자(=)를 사용하여 클래스 멤버 변수를 초기화했다:

class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;

public:
    Something()
    {
        // 이것은 모두 할당이지 초기화가 아니다.
        m_value1 = 1;
        m_value2 = 2.2;
        m_value3 = 'c';
    }
};

클래스 생성자가 실행될 때 m_value1, m_value2m_value3 변수가 생성된다. 그런 다음 생성자 본문이 실행되며, 멤버 변수값이 할당된다. 이 순서는 C++에서 다음 코드와 흐름이 비슷하다.

int m_value1;
double m_value2;
char m_value3;

m_value1 = 1;
m_value2 = 2.2;
m_value3 = 'c';

이 순서는 C++ 언어의 문법에서는 유효하지만, 초기화보다 효율적이지 않다.

그리고 일부 유형의 데이터(const 및 참조 변수)는 선언과 동시에 초기화해야 한다:

class Something
{
private:
    const int m_value;

public:
    Something()
    {
        m_value = 1; // error: const 상수 변수는 할당할 수 없다.
    } 
};

다음과 유사한 코드가 생성된다:

const int m_value; // error: const 상수 변수는 값으로 초기화되어야 한다.
m_value = 5; //  error: const 상수 변수는 할당할 수 없다.

이 문제를 해결하기 위해 C++은 생성자 멤버 초기화 리스트 (Constructor member initializer list)을 통해 멤버 변수를 초기화하는 방법을 제공한다.

여러 가지 변수 초기화 방법 포스트에서 변수를 초기화하는 세 가지 방법을 배웠다:

  • 복사 초기화 (copy initialization)
  • 직접 초기화 (direct initialization)
  • 유니폼 초기화 (uniform initialization)
int value1 = 1; // 복사 초기화
double value2(2.2); // 직접 초기화
char value3 {'c'}; // 유니폼 초기화

멤버 초기화 리스트를 사용하는 것은 직접 초기화(또는 C++ 11에서는 유니폼 초기화)를 수행하는 것과 유사하다.

class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;

public:
    Something()
    {
        // These are all assignments, not initializations
        m_value1 = 1;
        m_value2 = 2.2;
        m_value3 = 'c';
    }
};

위와 같은 예제를 이제 초기화 리스트를 사용해서 같은 코드를 작성해보겠다:

class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;

public:
    Something() : m_value1(1), m_value2(2.2), m_value3('c') // 멤버 초기화 리스트를 통해 멤버 변수를 직접 초기화할 수 있다.
    {
        // 멤버 변수를 여기서 할당할 필요가 없다.
    }

    void print()
    {
         std::cout << "Something(" << m_value1 << ", " << m_value2 << ", " << m_value3 << ")\n";
    }
};

int main()
{
    Something something;
    something.print();
    return 0;
}
This prints:
Something(1, 2.2, c)

멤버 초기화 리스트는 생성자 매개 변수 뒤에 삽입된다. 콜론 :으로 시작한 다음 초기화 할 각 변수를 쉼표로 구분하여 해당 변수의 값과 함께 나열한다. 또한, 초기화 리스트는 세미콜론 ;으로 끝나지 않는다.

Something() : m_value1(1), m_value2(2.2), m_value3('c')
{
    // ...
    // No need for assignment here
}

초기화 리스트가 멤버 변수 초기화 기능을 대체하므로 더는 생성자 본문에서 할당을 수행할 필요가 없다.


초기화 리스트는 호출자가 초기화 값을 전달할 때 더 유용하다:

#include <iostream>

class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;

public:
    Something(int value1, double value2, char value3='c')
        : m_value1(value1), m_value2(value2), m_value3(value3) // 멤버 초기화 리스트를 통해 멤버 변수를 직접 초기화할 수 있다.
    {
        // 멤버 변수를 여기서 할당할 필요가 없다.
    }

    void print()
    {
         std::cout << "Something(" << m_value1 << ", " << m_value2 << ", " << m_value3 << ")\n";
    }

};

int main()
{
    Something something(1, 2.2); // value1 = 1, value2=2.2, value3 gets default value 'c'
    something.print();
    return 0;
}#include <iostream>

class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;

public:
    Something(int value1, double value2, char value3='c')
        : m_value1(value1), m_value2(value2), m_value3(value3) // 멤버 초기화 리스트를 통해 멤버 변수를 직접 초기화할 수 있다.
    {
        // 멤버 변수를 여기서 할당할 필요가 없다.
    }

    void print()
    {
         std::cout << "Something(" << m_value1 << ", " << m_value2 << ", " << m_value3 << ")\n";
    }

};

int main()
{
    Something something(1, 2.2); // value1 = 1, value2=2.2, value3 gets default value 'c'
    something.print();
    return 0;
}
This prints:
Something(1, 2.2, c)

C++ 11의 유니폼 초기화

C++ 11에서는 직접 초기화 대신 유니폼 초기화를 사용할 수 있다:

class Something
{
private:
    const int m_value;

public:
    Something(): m_value { 5 } // 멤버 초기화 리스트를 통해 멤버 변수를 유니폼 초기화 할 수도 있다.
    {
    } 
};

나중에 상속을 수행할 때 초기화 리스트가 필요하므로 const 및 reference 변수를 사용하지 않더라도 이 초기화 리스트를 사용하는 게 좋다.


초기화 리스트를 사용한 배열 멤버 초기화

배열 멤버가 있는 클래스를 보자:

class Something
{
private:
    const int m_array[5];
};

C++ 11 이전에는 멤버 초기화 리스트를 통해서 배열 멤버를 0으로만 만들 수 있었다:

class Something
{
private:
    const int m_array[5];

public:
    Something(): m_array {} // zero the member array
    {
    }
};

그러나 C++ 11에서는 유니폼 초기화를 사용하여 멤버 배열을 완전히 초기화할 수 있다:


클래스인 멤버 변수 초기화

멤버 초기화 리스트를 사용해서 클래스인 멤버를 초기화할 수 있다:

#include <iostream>

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

class B
{
private:
    A m_a;
public:
    B(int y) : m_a(y-1) // call A(int) constructor to initialize member m_a
    {
        std::cout << "B " << y << "\n";
    }
};

int main()
{
    B b(5);
    return 0;
}
This prints:

A 4
B 5

변수 b가 생성되면 B(int) 생성자가 값 5로 호출된다. 생성자 본문이 실행되기 전에 m_a가 초기화되므로 값 4로 A(int) 생성자가 호출된다. 그러면 "A 4"가 인쇄되고, 그런 다음 제어는 B 생성자로 돌아가므로 B 생성자의 본문이 실행되어 "B 5"를 인쇄한다.


요약

  • 멤버 초기화 리스트를 사용하면 값을 할당하지 않고 멤버 변수를 초기화할 수 있다.

  • 생성자 본문에서 값을 할당하는 것보다 성능이 더 우수하다.

  • const 또는 reference 변수와 같이 초기값이 필요한 멤버를 초기화할 수 있는 유일한 방법이다.

  • 멤버 초기화 리스트는 기본 자료형 변수와 클래스 자체인 멤버 모두에서 잘 작동한다.


cpp 번역: 이 포스트의 원문은 https://www.learncpp.com/cpp-tutorial/8-5a-constructor-member-initializer-lists/ 입니다.


댓글 로드 중…

블로그 정보

소년코딩 - 소년코딩

소년코딩, 자바스크립트, C++, 물리, 게임 코딩 이야기

최근에 게시된 이야기