Game Develop

[C++]초기화리스트(Initilizer List)를 사용해야하는 경우. 본문

C++/C++

[C++]초기화리스트(Initilizer List)를 사용해야하는 경우.

MaxLevel 2022. 7. 9. 05:55

초기화리스트를 사용해야 하는 경우 2가지와 사용할 경우 이점을 설명해보겠다.

 

1. 클래스에서 상수형변수, 참조형변수를 멤버변수로 보유해야 할 경우.

상수형 변수와 참조형 변수는 둘 다 반드시 선언과 동시에 초기화를 시켜야 한다.

애초에 그렇게 하지 않으면 바로 에러가 나오기 때문에 컴파일도 못한다.

초기화리스트는 말 그대로 '초기화'이기 때문에, 생성자에서 원하는 값으로 초기화가 가능하다.

메인함수에서 인스턴스를 생성할 때 원하는 매개변수값으로 해당 인스턴스의 상수형변수, 참조형변수를 초기화시키는게 가능하다는 것이다. 아래의 예시를 보면 잘 되는걸 확인할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Point
{
public:
    Point(int _a, int& _b);
 
public:
    const int a;
    int& b;
};
 
Point::Point(int _a, int& _b) : a(_a), b(_b)
{
}
 
int main() {
 
    int x = 10;
    
    Point p(x, x);
 
    cout << "p.a : " << p.a << endl;
    cout << "p.b : " << p.b << endl;
 
    cout << "change x............ " << endl;
 
    x = 20;
 
    cout << "p.b : " << p.b << endl;
    return 0;
}
cs

 

결과창 :

에러없이 잘 빌드되는것도 확인되며, 메인함수에서 x의값을 변경한다음 p.b의 값을 찍었을 때 마찬가지로 값이 변경된 걸 로 보아 참조형변수도 제역할을 잘 수행한 것을 확인 할 수 있다.

 

 

2. 기본생성자가 없는 멤버객체의 초기화를 해야 할 경우

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class A {
public:
    int i;
public:
    A(int);
};
 
A::A(int arg) {
    i = arg;
    cout << "A's Constructor called: Value of i: " << i << endl;
}
 
// Class B contains object of A
class B {
    A a;
public:
    B(int);
};
 
B::B(int x) { 
    a.i = x; // 에러. A의 기본생성자가 없습니다.
}
 
//B::B(int x) :a(x) {  //Initializer list must be used
//    cout << "B's Constructor called";
//}
 
int main() {
    B obj(10);
    return 0;
}
cs

 

위 코드를 살펴보자. B obj(10);이라는 코드를 통해 B의 매개변수가 있는 생성자를 호출했다.

B는 A라는 클래스를 멤버변수로 가지고 있고, 생성자에서 A클래스 멤버변수의 i값을 x라는 값을 대입한다.

물론 옆에 주석처리처럼 에러가 뜬다. 말 그대로 A의 기본생성자가 없기 때문이다. 이게 왜 뜨는 것일까?

 

B의 인스턴스를 생성하게 된다면, B의 멤버변수인 A도 메모리에 할당하면서 생성될 것이다.

그 생성이라는 것을 할 때, 기본생성자를 호출해야하는데 A라는 클래스에는 기본생성자가 없다!

아예 A클래스에 어떠한 생성자도 작성해놓지 않았다면 컴파일러가 알아서 기본생성자를 만들어줬겠지만, 안타깝게도 위의 코드에는 int형 데이터타입을 매개변수로 받는 생성자가 작성되어 있다. 그렇기 때문에 A클래스에는 기본생성자가 없는것으로 판정이 나게되고, 위와같은 에러가 발생하는 것이다. (실제로 저 생성자를 주석처리하면 컴파일러가 기본생성자를 생성해 줄 것이기 때문에 에러가 발생하지 않는다)

 

하지만 아래와 같이 초기화리스트를 사용하면 위와 같은 에러들 없이 잘 수행된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class A {
public:
    int i;
public:
    A(int);
};
 
A::A(int arg) {
    i = arg;
    cout << "A's Constructor called: Value of i: " << i << endl;
}
 
// Class B contains object of A
class B {
    A a;
public:
    B(int);
};
 
//B::B(int x) { 
//    a.i = x;
//}
 
B::B(int x) :a(x) {  //Initializer list must be used
    cout << "B's Constructor called";
}
 
int main() {
    B obj(10);
    return 0;
}
 
cs

 

이유는 밑에서 언급할 성능과 연관이 있는데, 결론만 말하자면 초기화리스트를 사용하면 기본생성자를 호출을 안하기 때문이다. 바로 복사생성자로 초기화 해버리기 때문에, 기본생성자를 호출할 필요가 없다. 그래서 에러가 발생하지 않는것이다.

 

 

 

그러면 꼭 위와같은 경우들 말고, 일반적인 변수들을 사용하는 클래스에 초기화리스트를 사용해야하는 이유가 뭘까?

당연히 성능이 더 좋기 때문이다. 그렇다면 '왜?' 성능이 더 좋은가?

아래의 코드처럼 초기화리스트를 사용하지 않는 기존의 대입연산초기화를 봐보자.

 

 

Point(A _a)

{
   a = _a;

}

 

위의 코드에서는 어떤 작업이 수행되었을까? 총 두번의 작업이 수행된다. 

 

1. A의 기본생성자

2. A의 대입연산자

 

멤버변수 a를 생성할 때 A의 기본생성자를 수행했을 것이고, a = _a; 에서 A의 대입연산자를 수행한다.

하지만 초기화리스트를 사용하면 복사생성자 한번만 수행된다. 대입연산은 일어나지 않는다.

그렇기 때문에 초기화리스트는 성능상의 이점까지 가지고 있기 때문에 초기화리스트를 이용한 초기화를 하라고 권장한다.

 

 

 

'C++ > C++' 카테고리의 다른 글

[C++] Char*와 Char[]의 차이.  (0) 2022.08.17
[C++] Delete와 Delete[] 차이.  (0) 2022.07.11
[C++] const 관련 정리.  (0) 2022.01.05
[C++] 이동생성자.  (0) 2021.07.12
[C++] 대입연산자  (0) 2021.05.01