본문 바로가기
Program Language/c++

C++ 기초(유니폼 초기화)

by 토담이아빠 2023. 1. 27.

c++ 기초 유니폼 초기화

 

c++11부터 타입 초기화 방식을 통일시켰습니다. 중괄호 {....}을 사용하여 클래스, 구조체, 기본자료형, 포인터 등 모든 자료형에 대해서 초기화할 수 있습니다. 이번 장은 중괄호를 사용한 유니폼 초기화(uniform initialization)에 대해서 정리했습니다.


구조체와 클래스 초기화

 

c++11 이전에는 구조체와 클래스 초기화 방식이 서로 달랐습니다. 구조체는 중괄호로 초기화 가능하지만 클래스는 생성자를 이용한 함수형태로만 초기화가 가능했습니다. 다음 예제를 보시면 그 차이점을 알 수 있습니다.


#include <iostream>
 
struct Point_struct
{
	int x, y;
};
 
class Point_class
{
public:
	Point_class(int x, int y)
		:m_x(x), m_y(y) {}
private:
	int m_x;
	int m_y;
};
 
int main()
{
	Point_struct myPoint1 = { 10, 20 }; //구조체 초기화
	Point_class myPoint2(10, 20);       //클래스 초기화
 
	//do something...
	
	return 0;
}

유니폼 초기화 적용 이후에는 다음과 같이 초기화할 수 있습니다.


Point_struct myPoint1 = { 10, 20 }; //구조체 초기화
Point_class myPoint2 = { 10, 20 };  //클래스 초기화

또한 등호를 생략한 채로 초기화가 가능합니다.


Point_struct myPoint1{ 10, 20 }; //구조체 초기화
Point_class myPoint2{ 10, 20 };  //클래스 초기화

모든 대상 초기화

 

유니폼 초기화는 구조체나 클래스뿐만 아니라 c++내에 모든 타입에 대해서 초기화가 가능합니다. 아래는 이에 대한 일부 예를 보여줍니다.

 


int a1 = { 3 }; //일반 자료형 초기화
int a2{ 3 };    //일반 자료형 초기화
int* b1 = { &a1 };//포인터 초기화
int* b2{ &a1 };//포인터 초기화
Point_class* c1 = { new Point_class{10,20} }; //객체 포인터 초기화
Point_class* c2{ new Point_class{10,20}};     //객체 포인터 초기화
int* pArray1 = new int[4]{1, 2, 3, 4};        //동적할당 배열 초기화
int* pArray2{new int[4]{1, 2, 3, 4};          //동적할당 배열 초기화

영 초기화

 

유니폼 초기화는 변수를 영 초기화(제로 초기화)할 때도 적용할 수 있습니다. 다음과 같이 빈 중괄호 표시만 해주면 됩니다.


int a1{};              //값형 제로 초기화
int a2 = {};
double* b1{};          //포인터형 제로 초기화
double* b2 = {};
Point_struct c1{};     //구조체 제로 초기화
Point_struct* c2 = {};
Point_class d1{};      //클래스 제로 초기화(기본생성자가 정의되어 있어야 함)
Point_class d2 = {};

여기서 주의할 점은 클래스의 경우 기본생성자를 정의해줘야 멤버변수의 개수와 관계없이 초기화가 가능합니다. 그렇지 않으면 컴파일 에러가 발생합니다. 제로 초기화 시에는 각 타입에 맞게 초기값이 할당됩니다. 예를 들어 정수형 타입이라면 '0', 실수형 타입이라면 '0.00000000'으로 초기화됩니다. 또한 포인터 타입이라면 null 값인 '0x0000000000000000'으로 초기화가 됩니다

 

축소 변환 방지

 

유니폼 초기화를 사용하면 암묵적으로 일어나는 축소 변환을 방지할 수 있습니다. 예를 들면 다음과 같습니다.


void func(int i)
{
	std::cout << i << std::endl;
}
 
int main()
{
	
	int pi = 3.14;
	func(pi);
	
	return 0;
}

위의 경우 정수형 변수 pi에 실수값 3.14를 대입하면 실제로 대입되는 값은 3으로 줄어든 값이 대입됩니다. 이 경우 경고 메세지는 발생할 수 있지만 에러로 처리되지는 않습니다. 만약 에러로 처리하고 싶으면 다음 예제와 같이 유니폼 초기화를 사용하면 됩니다.


#include <iostream>
 
void func(int i)
{
	std::cout << i << std::endl;
}
 
int main()
{
	
	int pi = { 3.14 };
	func({ pi });
	
	return 0;
}

결과


auto타입에 대한 직접 리스트 초기화와 복제 리스트 초기화

 

유니폼 초기화는 다음 두가지 방법으로 초기화를 합니다.

  • 복제 리스트 초기화(copy list initialization) : T obj = {arg1, arg2, ...}
  • 직접 리스트 초기화(direct list initialization) : T obj{arg1, arg2, ...}

C++17부터는 유니폼 초기화 방법이 auto 타입에 대해서는 달라진 점이 있습니다. C++17 이전에는 auto타입에 대한 유니폼 초기화는 다음 예제처럼 전부 initializer_list<>로 처리했습니다. 


//복제리스트 초기화
auto a = { 10 };      //initializer_list<int>
auto b = { 10, 20 };  //initializer_list<int>
 
//직접리스트 초기화
auto c{ 10 };         //initializer_list<int>
auto d{ 10, 20 };     //initializer_list<int>

하지만 C++17 이후부터는 auto 타입에 대한 직접 리스트 초기화에 변화가 생겼습니다. 직접 리스트로 추론 시 하나의 값만 가능하다는 점입니다. 위 예제를 C++17 이후 버전에서 실행 시 다음과 같은 컴파일 에러가 발생합니다.


//복제리스트 초기화
auto a = { 10 };      //initializer_list<int>
auto b = { 10, 20 };  //initializer_list<int>
 
//직접리스트 초기화
auto c{ 10 };         //int
auto d{ 10, 20 };     //에러


마지막으로 유니폼 초기화 시 초기화 값들의 타입은 모두 같아야 합니다. 예를 들어 다음과 같이 작성하면 컴파일 에러가 발생합니다.


auto b = { 10, 20.0 };  //에러

 


[참고]

1. 전문가를 위한 c++(개정4)  (저자 : 마크 그레고리 / 옮긴이 남기혁)

2. 명품 c++ Programming (저자 : 황기태)

댓글