멤버 초기화 목록을 선호하는 이유는 무엇입니까?


228

생성자와 함께 멤버 초기화 목록을 사용하는 것에 부분적이지만 ... 이후의 이유를 잊어 버린 지 오래되었습니다 ...

생성자에서 멤버 초기화 목록을 사용합니까? 그렇다면 왜 그렇습니까? 그렇지 않다면 왜 안됩니까?


답변:


278

들어 POD의 클래스 멤버, 그것은 스타일의 단지 문제, 차이가 없습니다. 클래스 인 클래스 멤버의 경우 기본 생성자를 불필요하게 호출하지 않습니다. 치다:

class A
{
public:
    A() { x = 0; }
    A(int x_) { x = x_; }
    int x;
};

class B
{
public:
    B()
    {
        a.x = 3;
    }
private:
    A a;
};

이 경우에 대한 생성자 B는에 대한 기본 생성자를 호출 A한 다음 a.x3으로 초기화 합니다. 더 좋은 방법은 의 생성자가 초기화 목록에서 B의 생성자를 직접 호출 A하는 것입니다.

B()
  : a(3)
{
}

이것은 기본 생성자가 아닌 AA(int)생성자를 호출 합니다. 이 예제에서 그 차이는 무시할 만하지 만 A메모리를 할당하거나 파일을 여는 등의 기본 생성자가 더 많은 작업 을 수행한다고 가정 해보십시오 . 불필요하게 그렇게하고 싶지 않을 것입니다.

클래스는 기본 생성자가없는, 또는 당신이있는 경우 또한, const멤버 변수를, 당신은 해야한다 이니셜 라이저 목록을 사용하여 :

class A
{
public:
    A(int x_) { x = x_; }
    int x;
};

class B
{
public:
    B() : a(3), y(2)  // 'a' and 'y' MUST be initialized in an initializer list;
    {                 // it is an error not to do so
    }
private:
    A a;
    const int y;
};

5
또한 중요한 참고 자료입니다
4pie0

5
"a (3);"을 사용하지 않는 이유 또는 "a = A (3);" B의 기본 생성자 본문에?
Sergey

1
POD의 의미를 설명해 주시겠습니까?
Jonas Stein

2
@JonasStein POD는 완전한 클래스가 아닌 단순한 데이터 구조와 관련된 잘 정의 된 규칙 세트입니다. 이상 FAQ를 읽어 stackoverflow.com/questions/146452/what-are-pod-types-in-c
monkey0506

2
@Sergey, A의 기본 생성자는 여전히 호출됩니다.
Vassilis

44

위에서 언급 한 성능상의 이유 외에도 클래스가 생성자 매개 변수로 전달 된 객체에 대한 참조를 저장하거나 클래스에 const 변수가있는 경우 초기화 목록을 사용하는 것 외에는 선택할 수 없습니다.


7
내가 믿는 const 멤버들도 마찬가지입니다.
Richard Corden

예, 할당을 사용하여 const 변수를 수정할 수 없으므로 초기화해야합니다.
Hareen Laks

23
  1. 기본 클래스의 초기화

답변에 언급되지 않은 생성자 이니셜 라이저 목록을 사용하는 중요한 이유 중 하나는 기본 클래스의 초기화입니다.

건설 순서에 따라 기본 클래스는 하위 클래스보다 먼저 구성되어야합니다. 생성자 이니셜 라이저 목록이 없으면 기본 클래스에 기본 생성자가 있으면 자식 클래스의 생성자를 입력하기 직전에 호출됩니다.

그러나 기본 클래스에 매개 변수화 된 생성자 만있는 경우 생성자 이니셜 라이저 목록을 사용하여 기본 클래스가 자식 클래스보다 먼저 초기화되도록해야합니다.

  1. 매개 변수화 된 생성자 만있는 서브 오브젝트의 초기화

  2. 능률

생성자 이니셜 라이저 목록을 사용하면 먼저 데이터 멤버를 기본 상태로 초기화 한 다음 상태를 코드에서 필요한 상태로 변경하지 않고 코드에서 필요한 상태로 데이터 멤버를 초기화합니다.

  1. 비 정적 const 데이터 멤버 초기화

클래스의 정적이 아닌 const 데이터 멤버에 기본 생성자가 있고 생성자 이니셜 라이저 목록을 사용하지 않으면 기본 상태로 초기화되므로 의도 한 상태로 초기화 할 수 없습니다.

  1. 참조 데이터 멤버의 초기화

참조가 나중에 선언되고 초기화 될 수 없으므로 컴파일러가 생성자를 입력 할 때 참조 데이터 멤버를 초기화해야합니다. 이것은 생성자 이니셜 라이저 목록에서만 가능합니다.


10

성능 문제 외에도 코드 유지 관리 및 확장 성이라고 부르는 또 다른 중요한 문제가 있습니다.

T가 POD이고 초기화 목록을 선호하기 시작하면 한 번 T가 비 POD 유형으로 변경되면 불필요한 최적화 생성자 호출이 이미 최적화되어 있기 때문에 초기화 주위에서 아무것도 변경하지 않아도됩니다.

유형 T에 기본 생성자와 하나 이상의 사용자 정의 생성자가 있고 기본 생성자를 제거하거나 숨기려고 한 경우 초기화 목록이 사용 된 경우 사용자 정의 생성자가 코드를 업데이트 할 필요가 없습니다. 그들은 이미 올바르게 구현되었습니다.

const 멤버 또는 참조 멤버와 동일하게 처음에 T가 다음과 같이 정의되었다고 가정 해 봅시다.

struct T
{
    T() { a = 5; }
private:
    int a;
};

다음으로, 처음부터 초기화 목록을 사용하는 경우 const로 한정하기로 결정한 경우 한 줄 변경이지만 위와 같이 T를 정의한 경우 할당자를 제거하기 위해 생성자 정의를 파헤쳐 야합니다.

struct T
{
    T() : a(5) {} // 2. that requires changes here too
private:
    const int a; // 1. one line change
};

코드를 "코드 원숭이"가 아니라 자신이하고있는 일에 대해 더 깊이 고려하여 결정을 내리는 엔지니어가 코드를 작성하면 유지 관리가 훨씬 쉽고 오류가 덜 발생한다는 사실은 비밀이 아닙니다.


5

생성자의 본문이 실행되기 전에 상위 클래스 및 필드의 모든 생성자가 호출됩니다. 기본적으로 인수가없는 생성자가 호출됩니다. 초기화 목록을 사용하면 호출 할 생성자와 생성자가받을 인수를 선택할 수 있습니다.

참조 또는 const 필드가 있거나 사용 된 클래스 중 하나에 기본 생성자가없는 경우 초기화 목록을 사용해야합니다.


2
// Without Initializer List
class MyClass {
    Type variable;
public:
    MyClass(Type a) {  // Assume that Type is an already
                     // declared class and it has appropriate 
                     // constructors and operators
        variable = a;
    }
};

여기서 컴파일러는 다음 단계에 따라 MyClass
1 유형의 객체를 만듭니다 . "a"에 대해 유형의 생성자가 먼저 호출됩니다.
2.“Type”의 할당 연산자는 MyClass () 생성자의 본문 내에서 호출됩니다.

variable = a;
  1. 그리고 마지막으로“Type”의 소멸자는 범위를 벗어나기 때문에“a”가 필요합니다.

    이제 Initializer List가있는 MyClass () 생성자와 동일한 코드를 고려하십시오.

    // With Initializer List
     class MyClass {
    Type variable;
    public:
    MyClass(Type a):variable(a) {   // Assume that Type is an already
                     // declared class and it has appropriate
                     // constructors and operators
    }
    };

    이니셜 라이저 목록을 사용하면 다음 단계에 따라 컴파일러가 이어집니다.

    1. “Type”클래스의 복사 생성자가 다음과 같이 초기화됩니다. variable (a). 이니셜 라이저 목록의 인수는 구문 "변수"를 직접 복사하는 데 사용됩니다.
    2. “Type”의 소멸자는 범위를 벗어나므로“a”가 필요합니다.

2
이 코드 스 니펫은 문제를 해결할 수 있지만 코드 설명을 포함하면 게시물의 품질을 향상시키는 데 실제로 도움이됩니다. 앞으로 독자에게 질문에 대한 답변을 제공하고 있으며 해당 사람들이 코드 제안의 이유를 모를 수도 있습니다. 설명 주석으로 코드를 복잡하게 만들지 마십시오. 코드와 설명의 가독성이 떨어집니다! meta.stackexchange.com/q/114762/308249
davejal

2
자신의 이해를 작성하거나 그냥 복사하여 붙여 넣기 대신 원본 소스 (여기서 geeksforgeeks.com)에 대한 링크를 공유하십시오.
yuvi

1

추가 정보를 추가하여 멤버 초기화 목록이 얼마나 차이가 나는지 보여줄 수 있습니다 . leetcode 303 범위 합계 쿼리-불변, https://leetcode.com/problems/range-sum-query-immutable/ 에서 특정 크기의 벡터를 0으로 구성하고 초기화해야합니다. 다음은 두 가지 다른 구현 및 속도 비교입니다.

멤버 초기화 목록이 없으면 AC를 얻으려면 약 212ms가 소요 됩니다.

class NumArray {
public:
vector<int> preSum;
NumArray(vector<int> nums) {
    preSum = vector<int>(nums.size()+1, 0);
    int ps = 0;
    for (int i = 0; i < nums.size(); i++)
    {
        ps += nums[i];
        preSum[i+1] = ps;
    }
}

int sumRange(int i, int j) {
    return preSum[j+1] - preSum[i];
}
};

이제 멤버 초기화 목록을 사용하여 AC를 얻는 데 걸리는 시간은 약 108ms 입니다. 이 간단한 예제를 통해 멤버 초기화 목록이 더 효율적임을 알 수 있습니다. . 모든 측정은 LC의 실행 시간에서 이루어집니다.

class NumArray {
public:
vector<int> preSum;
NumArray(vector<int> nums) : preSum(nums.size()+1, 0) { 
    int ps = 0;
    for (int i = 0; i < nums.size(); i++)
    {
        ps += nums[i];
        preSum[i+1] = ps;
    }
}

int sumRange(int i, int j) {
    return preSum[j+1] - preSum[i];
}
};

0

통사론:

  class Sample
  {
     public:
         int Sam_x;
         int Sam_y;

     Sample(): Sam_x(1), Sam_y(2)     /* Classname: Initialization List */
     {
           // Constructor body
     }
  };

초기화 필요 목록 :

 class Sample
 {
     public:
         int Sam_x;
         int Sam_y;

     Sample()     */* Object and variables are created - i.e.:declaration of variables */*
     { // Constructor body starts 

         Sam_x = 1;      */* Defining a value to the variable */* 
         Sam_y = 2;

     } // Constructor body ends
  };

위 프로그램에서 클래스의 생성자가 실행될 때 Sam_xSam_y 가 생성됩니다. 그런 다음 생성자 본문에서 해당 멤버 데이터 변수가 정의됩니다.

사용 사례:

  1. 클래스의 상수 및 참조 변수

C에서는 변수 작성하는 동안 정의 해야합니다 . C ++에서와 마찬가지로 초기화 목록을 사용하여 객체를 만드는 동안 Const 및 Reference 변수를 초기화해야합니다. 객체 생성 후 (생성자 본문 내부) 초기화를 수행하면 컴파일 시간 오류가 발생합니다.

  1. 기본 생성자가없는 Sample1 (기본) 클래스의 멤버 객체

     class Sample1 
     {
         int i;
         public:
         Sample1 (int temp)
         {
            i = temp;
         }
     };
    
      // Class Sample2 contains object of Sample1 
     class Sample2
     {
      Sample1  a;
      public:
      Sample2 (int x): a(x)      /* Initializer list must be used */
      {
    
      }
     };

내부적으로 파생 클래스 생성자를 호출하고 기본 클래스 생성자를 호출하는 파생 클래스에 대한 객체를 만드는 동안 (기본값). 기본 클래스에 기본 생성자가 없으면 컴파일 시간 오류가 발생합니다. 피하기 위해, 우리는

 1. Default constructor of Sample1 class
 2. Initialization list in Sample2 class which will call the parametric constructor of Sample1 class (as per above program)
  1. 클래스 생성자의 매개 변수 이름과 클래스의 데이터 멤버는 동일합니다.

     class Sample3 {
        int i;         /* Member variable name : i */  
        public:
        Sample3 (int i)    /* Local variable name : i */ 
        {
            i = i;
            print(i);   /* Local variable: Prints the correct value which we passed in constructor */
        }
        int getI() const 
        { 
             print(i);    /*global variable: Garbage value is assigned to i. the expected value should be which we passed in constructor*/
             return i; 
        }
     };

우리 모두 알다시피, 두 변수의 이름이 같은 경우 우선 순위가 높은 로컬 변수가 전역 변수입니다. 이 경우 프로그램은 "i"값 {왼쪽 및 오른쪽 변수를 모두 고려합니다. 즉, Sample3 () 생성자 및 로컬 멤버 변수 (i)의 로컬 변수 인 i = i}가 재정의되었습니다. 피하기 위해 우리는

  1. Initialization list 
  2. this operator.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.