Maris 코딩
  • [C++] 멤버 이니셜라이저
    2024년 09월 10일 13시 09분 19초에 업로드 된 글입니다.
    작성자: Le마리

     

     

     

    함수 오버로드에 이어서 작성 

    지난 글을 보고오자

    ➥ 2024.09.09 - [공부/C++] - 함수 오버로드 (overLoad)

     

     

     

     

     

     

     

    멤버 이니셜라이저란 ?

    이니셜라이저를 사용하면 생성자가 실행될 때, 멤버 변수를 선언과 동시에 값을 할당할 수 있게 된다. 이를 통해 코드가 더 간결해지고, 초기화 과정이 더 효율적으로 진행된다.

     

     

     

    멤버 이니셜라이저생성자의 중괄호 {}가 시작되기 전에, 멤버 변수의 초기값을 설정하는 방식이다. 특히 **상수(const)**처럼 한 번 값이 설정되면 변경할 수 없는 변수는 꼭 이 방식으로 초기화를 해야 한다. 이걸 사용하지 않으면 오류가 발생할 수 있다.

     

     

    MyCharacter::MyCharacter(int hp, int dmg)
    {
        hitPoint = hp;
        baseDamage = dmg;
    }

     

     

     

    이것을 멤버 이니셜라이저를 사용하면 더 간결하게 바꿀 수 있다.

     

     

    MyCharacter::MyCharacter(int hp, int dmg) : hitPoint(hp), baseDamage(dmg)
    {
        // 생성자 블록 내부는 비워도 됩니다.
    }

     

     

     

     

     

     

     

    사용해보기

     

     

    1. 매개변수 없이 생성할 때

     

     

     

    1-1. 헤더파일 정의

     

    #pragma once
    class initialtest
    {
    public:
    	// 생성자 생성
    	initialtest();
    
    	// 출력
    	void ShowMe();
    
    private:
    	const int id;	// id를 상수로
    
    	int hitPoint;	// HP
    	int baseDamage;	// 기본 피해량
    };

     

     

     

     

     

     


    1-2. cpp에서 구현

     

    #include "initialtest.h"
    
    initialtest::initialtest()
    {
    };

     

     

    오류가 난다?

     

     

     

     

     

     

    오류가 나는 이유

    오류가 발생하는 이유는 id가 const 상수로 선언되었기 때문이다. const로 선언된 변수는 반드시 초기화가 필요하다. const는 한 번 값이 설정되면 변경할 수 없기 때문에, 생성자에서 초기화를 해주지 않으면 값을 설정할 수 없고, 오류가 발생하게 된다.

     

     

     

    왜 멤버 이니셜라이저를 써야 할까?

    1. 상수(const) 변수는 한 번 값이 설정되면 바꿀 수 없다. 그래서 생성될 때 바로 값을 넣어줘야 한다.
    2. 만약 {} 안에서 값을 설정하려고 하면, 이미 변수가 선언된 이후라서 값을 넣는 게 너무 늦은 것이다. 그래서 컴파일러가 오류를 낸다.
    3. 이 문제를 해결하려면 {} 전에 변수를 초기화할 수 있는 멤버 이니셜라이저를 사용한다. 이건 마치 프로그램이 시작하기 전에 미리 변수를 준비해두는 것과 같다.

     

    // 초기화를 멤버 이니셜라이저로 처리하는 생성자
    initialtest::initialtest() : id(1), hitPoint(100), baseDamage(10) {
        // 중괄호 {} 전에 :id(1) 이런 식으로 상수를 초기화
        // 만약 멤버 이니셜라이저를 사용하지 않으면 상수 id에 값을 줄 수 없어서 오류가 발생
    }

     

     

     

    아주 쉽게 비유하자면:

    • 멤버 이니셜라이저아침에 일어나기 전에 미리 옷을 준비해놓는 것과 같다. 아침에 일어나서 준비하려면 시간이 부족할 수 있잖으니까? 마찬가지로, {} 안에서 값을 설정하면 이미 늦는다. 그래서 그 전에 값을 미리 설정해둬야 한다.

     

    핵심 포인트:

    • const 변수는 꼭 생성될 때 바로 값이 설정돼야 하고, 그 방법이 바로 멤버 이니셜라이저.
    • 중괄호 {} 안에서 값을 설정하려고 하면, 이미 변수가 선언된 이후라서 오류가 생길 수 있다.

     

     

     

     

     

    1-3. cpp에서 제대로 구현

     

    #include "initialtest.h"
    #include <iostream>
    
    initialtest::initialtest() : id(0)	// : 다음 부분이 변수 초기화 (멤버 이니셜라이징)
    {
        // id가 상수로 지정되고, 초기값이 없으면 여기서 오류 확인 가능
        // 이유 : {}가 실행되면 이미 늦었기 때문 (상수 선언 후, 값이 없는 상태)
    
        // 해결 방법? 컴퓨터가 함수를 이해하는 형태 + 생성자의 실행 시점에 힌트
        // 컴퓨터가 함수의 구분을 하는 시점 = 파일을 읽을 때
        // 생성자의 실행 시점 = 최초로 클래스가 만들어질 때
        // -> 둘 다, 프로그래밍을 확정하기 "이전" (심지어 하나는 다 읽기도 전)
    
        // -> "늦기 전에 값을 정해주면 되겠네?" = {} 나오기 전에 코딩하면 되겠네?
    
        // c++에서 함수의 이름을 확정하고 = () 이후
        // 함수의 실행을 시작하기 전에 = {} 이전에 변수를 미리 할당해두는 방법 지원
    
        // = 변수 초기화 수단 (혹은 발음대로 '멤버 이니셜라이저' member initializer)
        //   ...작성법은 위 참조
    
        hitPoint = 100;
        baseDamage = 10;
    
        std::cout << "[기본 생성자로 생성한 멤버 이니셜라이징]" << std::endl;
    }
    void initialtest::ShowMe()
    {
        std::cout << "id : " << id << std::endl;
        std::cout << "hitPoint : " << hitPoint << std::endl;
        std::cout << "baseDamage : " << baseDamage << std::endl;
    }

     

     

     

     

     

     

     


    1-4. main에서 출력

     

    int main()
    {
    	// 이니셜라이저 호출을 통한 인스턴스 생성
    	initialtest* initialTest1 = new initialtest();
    
    	// 값은 cpp에서 기본값을 할당해줬으니 출력만 하면 된다
    	initialTest1->ShowMe();
    }

     

     

     

    == 결과 ==

     

     

     

     

     

     

     

     

     

     

     


     

     

     

     

     

     

     

     

    2. 숫자 둘을 사용하는 멤버 이니셜라이저

     

     


    2-1. 헤더에서 선언

     

    #pragma once
    class initialtest
    {
    public:
    	// 생성자 생성
    	initialtest();
    
    	// 출력
    	void ShowMe();
    
    private:
    	const int id;	// id를 상수로
    
    	int hitPoint;	// HP
    	int baseDamage;	// 기본 피해량
    
    public:
    	// 숫자 둘을 사용하는 생성자 선언
    	initialtest(int hp, int dmg);
    };

     

     

     

     

     

     

     

     


    2-2. cpp 에서 구현

     

    // 오버로드를 통한 생성자 작성
    initialtest::initialtest(int hp, int dmg) : id(0)
    {                                      // : 표시 = '벰버 이니셜라이저 실행'
                                           // () 밖 변수 이름 : 멤버 변수
                                           // () 안 데이터 : 초기 값
        hitPoint = hp;      // 블록 안에서 멤버 변수에 값을 할당
        baseDamage = dmg;  
    }

     

     

     

     

     

     

     

     

     

     


    2-3. main에서 출력

     

    					// 매개변수로 hp와 dmg를 썼으니 () 안에 숫자를 할당해줘야 한다.
    	initialtest* initialTest2 = new initialtest(1234, 5678);
    
    	initialTest2->ShowMe();

     

     

     

    == 결과 ==

     

     

     

     

     

     

     

     

     


     

     

     

     

     

     

     

    3. id를 포함한 숫자 셋짜리 선언 (제일 권장되는 방법)

        + ShoMe() 함수도 오버로드 해보자.

     

     

     


    3-1. 헤더에서 선언

     

    #pragma once
    class initialtest
    {
    public:
    	// 생성자 생성
    	initialtest();
    
    	// 출력
    	void ShowMe();
    
    	// ShowMe의 오버로드
    	void ShowMe(bool showId);	// id 출력 여부를 따로 계산하는 함수
    
    private:
    	const int id;	// id를 상수로
    
    	int hitPoint;	// HP
    	int baseDamage;	// 기본 피해량
    
    public:
    	// 숫자 둘을 사용하는 생성자 선언
    	initialtest(int hp, int dmg);
    
    	// id를 포함한 숫자 셋짜리 선언
    	initialtest(int id, int hp, int dmg);
    };

     

     

     

     

     

     


    3-2. cpp 에서 구현

     

     

    void initialtest::ShowMe(bool showDID)
    {
        if (showDID)    // 매개변수로 받은 값이 참이면
            std::cout << "[ID 가 있는 함수 출력]" << std::endl;
            std::cout << "id : " << id << std::endl;
    
        // 나머지 정보는 그대로 출력
        std::cout << "htiPoint : " << hitPoint << std::endl;
        std::cout << "baseDamage : " << baseDamage << std::endl;
    }
    
    
    // 오버로드를 통한 생성자 작성
    initialtest::initialtest(int hp, int dmg) : id(0)
    {                                      // : 표시 = '벰버 이니셜라이저 실행'
                                           // () 밖 변수 이름 : 멤버 변수
                                           // () 안 데이터 : 초기 값
        hitPoint = hp;      // 블록 안에서 멤버 변수에 값을 할당
        baseDamage = dmg;  
    }
    
    // 멤버 이니셜라이저 응용 : 모든 멤버 변수 써보기, 매개변수 써보기
    initialtest::initialtest(int id, int hp, int dmg) : id(id),
                                                        hitPoint(hp),
                                                        baseDamage(dmg)
        // : 표시 = 이니셜라이징 시작
        // 왼쪽의, () 밖 이름 : 멤머 변수를 의미
        // 오른쪽의, () 속 데이터 : 초기 값, 함수의 매개변수여도 OK
    {
        // 변수를 모두 이니셜라이저로 초기화해서 이 안에는 이제 코드가 필요없음
    }

     

     

     

     

     

     

     


    3-3. main에서 출력

     

    	// 세 개의 인자를 받는 함수 구현
    	initialtest* initialTest3 = new initialtest(1, 2345, 2427272);
    								// id를 외부에서 설정 가능한
    								// 숫자 세 개짜리 생성자 실행
    								// -> 작성할 때는 이니셜라이저로 다르게 썼지만
    								// 불러올 때는 그냥 평범하게 불러도 된다
    	// bool 데이터 쓴 함수 출력
    	initialTest3->ShowMe(true);

     

     

     

    == 결과 ==

     

     

     

     

     

     

     

    💡 3번이 제일 권장되는 이유

     

    더보기

    1. 멤버 이니셜라이저를 사용하면 "두 번 일하는 것을 막을 수 있다"

     

    일반적으로 멤버 변수를 초기화할 때, 생성자 내부에서 값을 할당하면 변수가 처음에 기본값으로 초기화된 후 다시 원하는 값으로 덮어쓰는 작업이 발생할 수 있다. 이렇게 하면 두 번 일하는 셈!

     

    비유: 만약 카페에서 커피를 주문한다고 생각해보자. 처음에 아무말도 하지 않으면 기본적으로 블랙 커피가 나올 것이다. 그런데 나중에 "우유랑 설탕 추가해 주세요!"라고 하면, 그 블랙 커피에 추가로 우유와 설탕을 넣어야 한다다.

    그러나 멤버 이니셜라이저를 사용하면, 처음부터 "우유와 설탕을 넣은 커피"를 바로 받을 수 있다.

     

     

     

    2. 상수나 참조자와 같은 멤버 변수는 생성자 블록 안에서 값을 할당할 수 없다

    **상수(const)**는 한 번 값이 정해지면 바꿀 수 없기 때문에, 생성자가 실행되기 전에 값을 정해줘야 한다. 이를 위해서는 반드시 멤버 이니셜라이저를 사용해야 한다.

    만약 상수나 참조자가 있다면 멤버 이니셜라이저 없이 생성자 블록 안에서 초기화할 수 없다.

     

    비유: 만약 커피를 주문했는데, "우유가 절대 추가될 수 없는" 상황이라면, 처음부터 "우유가 포함된 커피"를 요청해야만 하는 것과 같다!

     

     

     

    3. 더 명확한 코드

    멤버 이니셜라이저를 사용하면 어떤 변수에 어떤 값이 들어가는지가 생성자 코드 안에서 한눈에 명확하게 보인다. 이는 코드를 읽는 다른 사람이나 미래의 나에게 친절한 방식이다.

     

     

     

     

    4. 더 효율적인 메모리 사용

    컴퓨터가 변수를 초기화할 때 한 번에 바로 원하는 값으로 초기화할 수 있어서, 메모리를 더 효율적으로 사용할 수 있다.

     

     

     

     

     

     

     

    💡꼭 main에서 값을 넣어줘야 할까?

     

    더보기

    이니셜라이저를 사용하여 매개변수를 통해 값을 할당하는 방식에서는, 반드시 main에서 값을 전달해줘야 한다. 즉, 이 방식에서는 값을 넘겨주지 않으면, 초기화할 데이터가 없기 때문에 객체가 생성될 때 오류가 발생하거나 쓰레기 값이 들어갈 수 있다.

     

     

     

    다시 말해서, 다음과 같이 main에서 값을 넣어줘야 한다:

     

     

    	initialtest* initialTest3 = new initialtest(1, 2345, 2427272);
        // initialTest3는 id가 1, hitPoint가 2345, baseDamage가 2427272인 상태로 생성됩니다.

     

     

     

     

    만약 main에서 값을 넣지 않고 객체를 생성하려고 한다면, 기본 생성자가 필요하고, 그 경우에는 미리 정해둔 기본값을 설정하게 됩니다.

     

    예시 (기본 생성자도 같이 사용)

     

     

    // 기본 생성자
    initialtest::initialtest() : id(0), hitPoint(100), baseDamage(50) {
        // 기본값을 사용
    }
    
    // 매개변수를 사용하는 생성자 (이니셜라이저)
    initialtest::initialtest(int id, int hp, int dmg) : id(id), hitPoint(hp), baseDamage(dmg) {}

     

     

     

     

    이렇게 하면, main에서 기본 생성자를 사용할 경우에는:

     

     

    int main() {
        initialtest obj1; // 기본 생성자 사용 -> id = 0, hitPoint = 100, baseDamage = 50
        initialtest obj2(1, 100, 200); // 매개변수 생성자 사용 -> id = 1, hitPoint = 100, baseDamage = 200
    }

     

     

     

    정리 

    • 매개변수 생성자를 사용할 때는 main에서 값을 꼭 전달해야 한다.
    • 기본 생성자를 함께 정의해두면, 값을 전달하지 않고도 객체를 생성할 수 있다.

     

     

     

     

     

     

     

     

     


     

     

     

     

     

     

     

     

     

    이니셜라이저의 장점과 한계

     

    1. 멤버 이니셜라이저가 좋은 이유

    • 컴퓨터 효율성: 이니셜라이저는 변수를 바로 초기화하므로 메모리 사용 면에서 더 빠르고 효율적이다.
    • 상수 멤버 처리: const로 선언된 변수나 reference는 생성자 본문에서 값을 할당할 수 없기 때문에, 이니셜라이저를 사용해야 한다.
    • 직관성: 이니셜라이저는 멤버 변수를 생성자 호출 시 바로 초기화하므로 코드가 간결하고 초기화 과정이 명확히 드러난다.

     

    2. 항상 좋은 건 아니다

    • 다른 클래스보다 나중에 초기화가 필요한 경우: 예를 들어, 어떤 클래스가 다른 클래스의 데이터를 참고해야 할 때 이니셜라이저로 즉시 초기화하는 것은 적절하지 않다. 온라인 게임에서 퀘스트나 등장인물 데이터를 먼저 불러온 후에 클래스를 초기화하는 경우가 그렇다.
    • 가독성 문제: 때로는 a = n;과 같이 직접 할당하는 방식이 더 명확할 수 있다. 이 방식은 사람이 나중에 코드를 읽을 때 이해하기 쉬워 가독성을 높인다.

     

    3. 이니셜라이저의 한계

    • 이니셜라이저는 해당 클래스의 멤버만 초기화 가능: 이니셜라이저는 해당 클래스 내에서 선언된 멤버 변수만 초기화할 수 있다. 만약 다른 클래스의 데이터를 초기화해야 한다면 평범한 코딩 방식이 더 적합하다.

    결론

    • 상황에 맞게 선택: 이니셜라이저는 효율적일 수 있지만 모든 경우에 적합하지 않다. 변수의 초기화 시점과 관계에 따라 이니셜라이저를 사용할지, 아니면 일반적인 코딩 방식을 사용할지 결정해야 한다.

     

     

     

     

     

     

     

     

     

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

    [C++] 함수 오버라이딩 (Overriding)  (5) 2024.09.17
    [C++] 상속 (inheristance)  (3) 2024.09.10
    [C++] 함수 오버로드 (overLoad)  (0) 2024.09.09
    [C++] 생성자를 이용해 캐릭터 만들기  (0) 2024.09.09
    [C++] 소멸자  (0) 2024.09.09
    댓글