Program Language/C & C++

[C++] initializer-list 생성자

야곰야곰+책벌레 2022. 1. 14. 15:13
728x90
반응형

std::initializer_list 유형의 단일 인수를 사용하는 생성자를 초기화 목록 생성자라고 한다. 초기화 목록 생성자는 {} 목록을 초기화 값을 사용하여 객체를 생성하는 데 사용된다. 표준 라이브러리 컨테이너(예:벡터 및 맵)에는 초기화 목록 생성자, 할당 등이 있다.

vector<double> v = { 1, 2, 3.456, 99.99 };

list<pair<string,string>> languages = {
	{"Nygaard","Simula"}, {"Richards","BCPL"}, {"Ritchie","C"}
};

map<vector<string>,vector<int>> years = {
	{ {"Maurice","Vincent", "Wilkes"},{1913, 1945, 1951, 1967, 2000} },
	{ {"Mar tin", "Richards"} {1982, 2003, 2007} },
	{ {"David", "John", "Wheeler"}, {1927, 1947, 1951, 2004} }
};

{} 목록을 수락하는 메커니즘은 std::initializer_list<T> 유형의 인수를 취하는 함수(종종 생성자)이다. 

void f(initializer_list<int>);

f({1,2});
f({23,345,4567,56789});
f({}); // the empty list

f{1,2}; // error : function call () missing

years.insert({{"Bjarne","Stroustrup"},{1950, 1975, 1985}});

initailizer-list는 임의의 길이일 수 있지만 동종이어야 한다. 즉, 모든 요소는 템플릿 인수 유형 T이거나 암시적으로 T로 변환 가능해야 한다.

initializer_list 생성자 명확성

클래스에 여러 생성자가 있을 경우 일반적인 오버로드 해결 규칙을 사용하여 주어진 인수 집합에 대해 올바른 생성자를 선택한다. 생성자를 선택하려면 기본 및 초기화 목록이 우선한다.

struct X {
	X(initializer_list<int>);
	X();
	X(int);
};

X x0 {}; // empty list: default constructor or initializer-list constructor? (the default constructor)
X x1 {1}; // one integer: an int argument or a list of one element? (the initializer-list constructor)

규칙은 다음과 같다.

  • 기본 생성자 또는 initializer-list 생성자를 호출할 수 있는 경우 기본 생성자를 선호한다.
  • initializer-list 생성자와 '보통 생성자'를 모두 호출할 수 있는 경우 initializer-list 생성자를 선호한다.

첫 번째 규칙인 '기본 생성자 선호'는 기본적인 상식이다. 가능한 한 가장 간단한 생성자를 선택하자. 또한 기본 생성자가 하는 것과 다른 빈 목록으로 작업을 수행하도록 초기화 목록 생성자를 정의하면 수작업에 의한 디자인 오류가 있을 것이다.

두 번째 규칙인 'initializer-list 생성자 선호'는 요소 수에 따라 다른 해결 방법을 피하기 위해 필요하다. std::vector를 살펴보자.

vector<int> v1 {1}; // one element
vector<int> v2 {1,2}; // two elements
vector<int> v3 {1,2,3}; // three elements
vector<string> vs1 {"one"};
vector<string> vs2 {"one", "two"};
vector<string> vs3 {"one", "two", "three"};

모든 경우에 initializer-list 생성자가 사용된다. 하나 또는 두 개의 정수 인수를 사용하여 생성자를 호출하려면 () 표기법을 사용해야 한다.

vector<int> v1(1); // one element with the default value (0)
vector<int> v2(1,2); // one element with the value 2

initializer_list 사용

initializer_list<T> 인수가 있는 함수는 멤버 함수 begin(), end() 및 size()를 사용하여 시퀀스로 액세스 할 수 있다.

void f(initializer_list<int> args)
{
	for (int i = 0; i!=args.size(); ++i)
		cout << args.begin()[i] << "\n";
}

불행히도 initializer_list는 첨자를 제공하지 않는다.

initializer_list<T>는 값으로 전달된다. 이는 오버로드 해결 규칙에 의해 요구되며 initializer_list<T> 객체가 T배열에 대한 작은 핸들(일반적으로 두 단어)이기 때문에 오버헤드를 부과하지 않는다.

해당 루프는 다음과 같이 동일하게 작성될 수 있다.

void f(initializer_list<int> args)
{
	for (auto p=args.begin(); p!=args.end(); ++p)
	cout << ∗p << "\n";
}
void f(initializer_list<int> args)
{
	for (auto x : args)
	cout << x << "\n";
}

initializer_list를 명시적으로 사용하려면 <initializer_list>가 정의된 헤드 파일을 #include 해야 한다. 그러나 vector, map 등은 initializer_lists를 사용하기 때문에 헤드는 이미 #include<initializer_list>가 필요 없다.

initializer_list의 요소는 변경할 수 없다. 그들의 가치를 수정할 생각은 하지 말자.

int f(std::initializer_list<int> x, int val)
{
	∗x.begin() = val; // error : attempt to change the value of an initializer-list element
	return ∗x.begin(); // OK
}
void g()
{
	for (int i=0; i!=10; ++i)
		cout << f({1,2,3},i) << '\n';
}

f()의 할당이 성공했다면 1 값이 변경될 수 있는 것처럼 보인다. 그것은 가장 기본적인 개념 중 일부에 심각한 손상을 입혔을 것이다. initializer_list 요소는 변경할 수 없으므로 이동 생성자를 적용할 수 없다.

컨테이너는 다음과 같은 initializer-list 생성자를 구현할 수 있다.

template<class E>
class Vector {
public:
	Vector(std::initializer_list<E> s); // initializer-list constructor
	// ...
private:
	int sz;
	E∗ elem;
};

template<class E>
Vector::Vector(std::initializer_list<E> s)
	:sz{s.size()} // set vector size
{
	reserve(sz); // get the right amount of space
	uninitialized_copy(s.begin(), s.end(), elem); // initialize elements in elem[0:s.size())
}

initializer 목록은 보편적이고 균일한 초기화 디자인의 일부다.

직접 및 복사 초기화

직접 초기화와 복사 초기화의 구분은 {} 초기화에 대해 유지된다. 컨테이너의 경우 이는 구분이 컨테이너와 해당 요소 모두에 적용됨을 의미한다. 

  • 컨테이너의 initializer-list 생성자는 explicit 이거나 아닐 수 있다.
  • initializer-list의 요소 유형 생성자는 explicit 이거나 아닐 수 있다.

vector<vector<double>>의 경우 요소에 적용된 직접 초기화와 복사 초기화 구분을 볼 수 있다.

vector<vector<double>> vs = {
	{10,11,12,13,14}, // OK: vector of five elements
	{10}, // OK: vector of one element
	10, // error : vector<double>(int) is explicit
    
	vector<double>{10,11,12,13}, // OK: vector of five elements
	vector<double>{10}, // OK: vector of one element with value 10.0
	vector<double>(10), // OK: vector of 10 elements with value 0.0
};

컨테이너에는 명시적 생성자와 그렇지 않은 생성자가 있을 수 있다. 표준 라이브러리 vector가 그 예다. 예를 들어 std::vector<int>(int)는 explicit이지만 std::vector<int>(initializer_list<int>)는 그렇지 않다.

vector<double> v1(7); // OK: v1 has 7 elements; note: uses () rather than {}
vector<double> v2 = 9; // error : no conversion from int to vector
void f(const vector<double>&);
void g()
{
	v1 = 9; // error : no conversion from int to vector
	f(9); // error : no conversion from int to vector
}

()을 {}로 바꾸면 다음을 얻을 수 있다.

vector<double> v1 {7}; // OK: v1 has one element (with the value 7)
vector<double> v2 = {9}; // OK: v2 has one element (with the value 9)
void f(const vector<double>&);
void g()
{
	v1 = {9}; // OK: v1 now has one element (with the value 9)
	f({9}); // OK: f is called with the list {9}
}

분명히 결과는 극적으로 다르다.

이 예는 가장 혼란스러운 경우의 예를 제공하기 위해 세심하게 제작되었다. 명백한 모호성(독자의 눈에는, 컴파일러에게는 아닌)이 더 긴 목록에는 나타나지 않는다.

vector<double> v1 {7,8,9}; // OK: v1 has three elements with values {7,8,9}
vector<double> v2 = {9,8,7}; // OK: v2 has three elements with values {9,8,7}

void f(const vector<double>&);
void g()
{
	v1 = {9,10,11}; // OK: v1 now has three elements with values {9,10,11}
	f({9,8,7,6,5,4}); // OK: f is called with the list {9,8,7,6,5,4}
}

유사하게, 정수가 아닌 유형의 요소 목록에는 잠재적인 모호성이 발생하지 않는다.

vector<string> v1 { "Anya"}; // OK: v1 has one element (with the value "Anya")
vector<string> v2 = {"Courtney"}; // OK: v2 has one element (with the value "Courtney")
void f(const vector<string>&);
void g()
{
	v1 = {"Gavin"}; // OK: v1 now has one element (with the value "Gavin")
	f({"Norah"}); // OK: f is called with the list {"Norah"}
}

 

728x90
반응형

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

[C++] 함수 delete  (0) 2022.01.17
[C++] static 멤버 초기화  (0) 2022.01.17
[C++] 클래스 static 멤버  (0) 2022.01.14
[C++] 클래스 가변성(mutability)  (0) 2022.01.14
[C++] 클래스 explicit 생성자  (0) 2022.01.14