C, C++/C++ 언어 / / 2024. 8. 31.

[C++] 초기화( initialization )의 종류 정리

변수의 초기화

C++을 사용해서, 코딩을 하다 보면 의외로 다양한 방법으로 변수를 초기화할 수 있음을 알게 됩니다.

( 물론, 다른 언어도 다양한 방식의 초기화 방법을 제공합니다. )

 

그런데, 대부분은 자기만의 특정한 방식의 초기화 방법을 선호하고, 또 그렇게 하더라도 문제가 되는 일이 거의 없습니다.

그렇지만, 방법 간의 미묘한 차이점이 있기 때문에, 한 가지 방법만으로는 모든 상황을 만족시킬 수는 없는 경우가 있습니다.

 

C++에서 변수를 초기화려면, 아래의 세 가지 중에 한 가지 방법을 사용할 수 있습니다.

 

  1. 복사 초기화( copy initialization )
  2. 직접 초기화( direct initialization )
  3. 균일 초기화( uniform initialization )

 

아무리, 초기화의 방법이 복잡하고 다양해 보이더라도 결국은 이 세 가지 방법 중의 한 가지라는 것이죠.

 

복사 초기화( copy initialization )

복사 초기화는 등호= 를 사용해서, 변수의 값을 초기화하는 방법입니다.

int length = 15;    // 복사 초기화

변수에 설정하고픈 초기값이 등호 다음에 위치하게 됩니다.

 

이 초기화 방식은 암시적인 복사나 변환이 발생할 때 사용되기도 합니다.

예를 들어, 함수에 인자가 값으로 전달될 때나, 함수에서 값을 반환할 때, catch 블록에서 값으로 예외를 받을 때, 복사 초기화가 사용됩니다.

 

그렇기 때문에, 암시적으로 클래스의 생성자가 호출되는 것을 막기 위해 사용되는 explicit 키워드는, 복사 초기화를 사용할 수 없도록 만듭니다.

explicit에 관한 내용은 여기에서 볼 수 있습니다.

 

[C++] explicit 키워드와 변환 생성자( converting constructor )

변환 생성자( Converting Constructor )가 무엇인가변환 생성자는 암시적인 타입 변환을 통해 호출되는 생성자를 말합니다.그리고, 이 생성자를 통해서 임시 객체가 생성됩니다. 먼저, 암시적인 타입

codingembers.tistory.com

 

또한, 클래스 객체의 초기화는, 원시 타입과는 다르게, 클래스 생성자를 통하여 이루어진다는 것은 알고 있을 겁니다.

다음의 예제에선, 대입 초기화 방법을 통해서 복사 생성자가 호출되는 것을 보여줍니다.

#include <iostream>
using namespace std;

class CWidjet{
public:

    CWidjet() = default;    // 컴파일러가 만드는 기본 생성자를 사용

    CWidjet(const CWidjet& c){
        cout << "Copy Constructor called\n";
    }

    void operator =(const CWidjet& c){
        cout << "Copy Assignment Operator called\n";
    }
};

int main(){

    CWidjet widjet;
    CWidjet other = widjet;  // 대입 초기화

    CWidjet c;
    c = widjet; // 대입 연산
}

▼출력

Copy Constructor called
Copy Assignment Operator called

위의 마지막 문장은 대입 초기화가 아니라, 대입 연산에 해당합니다.

이 대입 연산은 복사 대입 연산자를 호출하게 됩니다.

 

참고로, 다음 문장은 컴파일러가 자동으로 생성하는 생성자를 사용하겠다는 것을 명시하는 구문입니다.

CWidjet() = default;    // 컴파일러가 만드는 기본 생성자를 사용

 

직접 초기화( direct initialization )

직접 초기화는 초기화하려는 값을 괄호( ) 안에 넣어서 초기화하는 방법입니다.

이 초기화 방법은 주로 클래스 객체를 초기화하는 경우에 사용됩니다.

class CWidjet{

    int m_nVal;
    string m_str;

public:

    CWidjet(int n, const string& str) : m_nVal(n), m_str(str){}
};

int main(){

    CWidjet widjet(10, "direct initialization"); // 직접 초기화
}

 

다음은 double 타입의 값을 사용하여, int 타입의 변수를 초기화는 예문입니다.

int intVal( 6.5 );	// 직접 초기화

이 경우, 8바이트의 double 타입이 4바이트( 사용하는 환경에 따라 int의 크기가 다를 수 있습니다. )의 int 타입으로 변환이 됩니다.

그래서, 여기서는 6.5의 값이 6으로 변환됩니다.

이렇게, 타입이 차지하는 크기가 줄어드는 변환을 좁히기 변환( narrowing conversion )이라고 합니다.

 

균일 초기화( uniform initialization )

균일 초기화는 초기화하려는 값을 중괄호{ } 안에 넣어서 초기화하는 방법입니다.

이 방법을 리스트 초기화( list initialization )이나 중괄호 초기화( brace initialization )라고 부르기도 합니다.

 

이 균일 초기화의 방법은 좀 더 세밀하게 세 가지로 나눌 수도 있습니다.

int length{ 5 };        // direct uniform initialization
int height = { 10 };    // copy uniform initalization
int val{ };             // value uniform initialization ( 0으로 초기화 )

 

이 방식이 uniform 이름을 가지게 된 것은, 이전 항목의 두 가지 방법( 복사, 직접 초기화 )을 사용할 수 없는 모든 상황에서도, 이 초기화 방법은 사용 가능하기 때문입니다.

 

아래의 예문은 변수 vec 객체의 원소들을 균일 초기화 방식으로 채우는 방법을 보여줍니다.

int main(){
    vector<int> vec = { 1, 2, 3, 4, 5 };	// 균일 초기화
}

위 예문은 vector에 1,2,3,4,5의 값을 갖는 5개의 원소를 생성합니다.

다른 초기화 방법은 이러한 방법을 제공할 수 없습니다.

 

참고로, 다음의 직접 초기화는 vec 안에, 2의 값을 갖는 5개의 원소를 생성합니다.

그렇지만, 아래의 균일 초기화는 값 5와 2를 갖는 두 개의 원소를 생성합니다.

vector<int> vec( 5, 2 );	// direct initialization

vector<int> vec2{ 5, 2 };	// uniform initialization

 

  또, C++11 이후부턴, 클래스의 비정적( non-static ) 멤버의 초기화를 선언과 동시에 바로 실행할 수 있게 되었습니다.

그렇지만, 이 경우에 직접 초기화 방법은 사용할 수 없습니다.

class CWidjet2{

    int x1 = 10;    // copy initialization
    int x3{ 10 };   // uniform initialization
    int x2(10);     // direct initialization. error !
};

 

  그리고, 다음 객체는 위에서 말한 대로 explicit 키워드를 사용합니다.

이 객체는 다른 초기화 방법은 가능하지만, 복사 초기화 방법은 사용할 수 없습니다.

class CWidjet3{

    int m_val;
public:
    explicit CWidjet3( int val) : m_val(val) {}
};

int main(){

    CWidjet3 w1{3};
    CWidjet3 w2(3);
    CWidjet3 w3 = 3;	// 복사 초기화. error !
}

 

  마지막으로, 값 균일 초기화( value uniform initialization )는 변수 val의 값을 0으로 초기화하게 됩니다.

int val{ };	// int val{ 0 } 과 같은 의미

그런데, 아래의 문장은 직접 초기화( direct initialization )가 아니라, 함수의 전방 선언을 말하는 것이 됩니다.

int val2(); 	// 변수를 초기화하는 문장이 아님
int val3(0);    // 위 문장과 비교를 하기 위한 문장

cout << typeid(val2).name() << ", " << typeid(val3).name();

▼출력

FivE, i

위의 출력은 줄임말을 이용한 val2와 val3의 타입을 나타냅니다. ( 물론 위 코드를 실행시킨 환경에 따란 결과가 다를 수 있습니다 )

보는 바와 같이 val2는 int 타입의 변수가 아닌 것을 알 수 있습니다.

그리고, 마지막 문장은 위와 달리, val3을 직접 초기화( direct initialization ) 방법을 통해서 0으로 초기화하였습니다.

 


이러한 균일 초기화 방법은 다음과 같은 특징을 갖고 있습니다.

 

먼저, 균일 초기화는 이전 항목에서 말한 좁히기 변환( narrowing conversion )을 금지합니다.

이것은 균일 초기화 동안에 발생하는 데이터의 손실을 막기 위한 것입니다.

따라서, 다음과 같은 예문은 오류를 발생합니다.

int main(){

    int x1 = 6.5;    // 복사 초기화
    int x2(3.2);     // 직접 초기화
    int x3{4.2};     // 균열 초기화. error !
}

 

그리고, 복사 균일 초기화( copy uniform initialization )를 통한 변수를 초기화한 경우, auto 키워드를 사용한 변수의 타입은 std::initializer_list로 추론됩니다.

이것은 auto 키워드의 데이터 타입 추론 규칙에 따른 것입니다.

int main(){

    auto x1 = 27;
    auto x2( 27 );
    auto x3 = { 27 };	// copy uniform initialization
    auto x4{ 27 };

    cout << typeid(x1).name() << endl;
    cout << typeid(x2).name() << endl;
    cout << typeid(x3).name() << endl;
    cout << typeid(x4).name() << endl;
}

▼출력

i
i
St16initializer_listIiE
i

 

또한, std::initializer_list를 매개 변수로 하는 생성자를 추가한 클래스는 균일 초기화를 사용 시, 원하는 생성자를 호출하고 있는지 주의할 필요가 있습니다.

CWidjet4( std::initializer_list li );	// initializer_list 매개 변수의 생성자

이것은 컴파일러가 균일 초기화 만나게 되면, 가능한 경우, 이 중괄호 초기화 식을 std::initializer_list로 변환하려는 특성 때문입니다.

 

이 std::initializer_list에 관한 내용은 다음 글에서 볼 수 있습니다.

 

[C++] 균일 초기화( uniform initialization )과 std::initializer_list

initializer_list의 소개배열을 여러 원소들로 초기화하려면, 다음과 같이 균일 초기화( uniform initialization )를 사용하면 됩니다.int main(){ int intArray[] { 1, 2, 3, 4, 5 }; // uniform initialization for( int x : intArray)

codingembers.tistory.com

 

 

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유