[C++11] auto,decltype,array

이번엔 C++11에서 새롭게 나온 auto와 decltype에 대해 알아보자.

auto는 타입추론 기능이다. 하나의 자료형으로 볼 수 있으며,

컴파일 타임에 해당 자료형이 결정된다.

auto a=5;    //a는 int로 취급
auto b=2.;   //b는 double로 취급
vector<int> v={1,2,3};
auto it=v.begin();    //it는 vector<int>::iterator 로 취급

우선 바보가 아닌이상 이 auto 키워드는 다 이해했을 거라고 본다.

보통은 STL 컨테이너의 반복자 타입을 사용할때 auto를 사용한다

사실 auto는 원래 C언어에서 기본 기억저장소 라는 뜻이였다.

(전역변수면 Data영역에 지역변수면 Stack에 만들어라...라는 뜻이다.)

하지만 ㅋ 거의 뜻이 없다고 봐도 무방한데, 이것이 C++11에 오면서 새로운 키워드로 거듭났다.

물론 이제 auto int i=0; 이런 구문은 C++11에서 허용하지 않는다.

이제 C++11에서 auto는 반복자를 위해서 사용하는것이 옳다고 본다.

#include<iostream>		//std::cout , std::endl
#include<algorithm>		//for_each
#include<cstdlib>		//EXIT_SUCCESS
using namespace std;
int main(){
	int arr[5] = { 2, 5, 1, 3, 4 };
	//전통적인 C  스타일의 for
	for (int i = 0; i < sizeof(arr) / sizeof(int); i++){
		cout << arr[i] << endl;
	}
	//반복자를 이용한 for
	for (auto it = begin(arr); it != end(arr); it++){
		cout << *it << endl;
	}
	//c++11에 새로나온 range-based-for
	for (auto& e : arr){
		cout << e << endl;
	}
#if	defined(_WIN64) || defined(_WIN32)
	//VC++에만 있는 for each (그러나 사용하지 않는것을 추천함)
	//range-based-for 문으로 대체하여 사용하길 권장함
	for each(auto& e in arr){
		cout << e << endl;
	}
#endif	//defined(_WIN64) || defined(_WIN32)
	//algorithm에 있는 for_each
	for_each(begin(arr), end(arr), [](int& e)->void{cout << e << endl;});
	return EXIT_SUCCESS;
}

뭐...보다 시피 여러가지 방법의 for 가 있다.

그 중에서 for each 는 VC++ 에서만 사용가능하지만 사용하지 말라고 대놓고 써놨다.


https://msdn.microsoft.com/ko-kr/library/ms177202.aspx

이제 머리속에서 for each는 잊으면 된다.

명심할 점은 for eachfor_each는 다른거다.

그럼 잠시 for 문 이야기를 해보자.

1. 어떤 for문을 사용해야 하나요??

C언어에서는 for , while , do while 의 3가지 반복문이 있다.

do while은 사용시기가 정해져 있고, for는 특정 횟수를, 즉 n번 반복할때

while은 정확히 몇번 돌지 모를때 사용한다.

C++언어에서는 자기 사용하고 싶은거 사용하면 된다.

range-based-for 는 컨테이너의 전체를 반복할때 사용하면되고 나머지는 때에 맞춰 사용하면 된다.

2. C++ 스타일의 배열

사실 반복문 보다 중요한게 바로 C++의 배열이다.

C++에 대해 이야기를 좀 하자면, C++은 완전한 객체지향 언어가 아니다.

객체지향을 지원하는 언어이다. JAVA나 C#같은 완전한 객체지향언어는 아니지만,

적어도 C의 잔여물은 사용하지 않았으면 한다.

std::array

c++11에서 새로이 추가된 array라는 컨테이너가 있다.

C의 정적 배열을 대신하는 컨테이너로 #include<array>를 포함하여 사용할 수 있다.

std::array는 첨자 접근을 아래의 3가지 방법으로 할 수 있다.

#include<iostream>		//std::cout , std::endl , std::cerr
#include<cstdlib>		//EXIT_SUCCESS
#include<array>			//std::array , std::get	
#include<stdexcept>		//std::out_of_range
using namespace std;
int main(){
	array<int, 5> arr = { 2, 5, 1, 3, 4 };
	//바로 접근하는 operator[]
	cout << arr[2] << endl;			
	try{
		//out_of_range 예외를 던지는 at()
		cout << arr.at(2) << endl;		
	}
	catch (out_of_range& e){
		cerr << e.what() << endl;
	}
	//범위 위반을 컴파일타임에 검사하는 std::get
	//오직 array와만 호환된다.
	cout << get<2>(arr) << endl;
	return EXIT_SUCCESS;
}

그리고 array는 relational operators를 제공한다. 우리가 아는 비교연산과 유사하며

비교는 같은 크기의 std::array 만 가능하며, strcmp와 비슷하게 첨자별로 순서대로 비교한다.

#include<iostream>		//std::cout , std::endl
#include<cstdlib>		//EXIT_SUCCESS
#include<array>			//std::array
using namespace std;
int main(){
	array<int, 5> a = { 1, 2, 3, 4, 5 };
	array<int, 5> b = { 2, 2, 2, 2, 2 };
	array<int, 5> c = { 2, 2, 2, 2, 2 };
	if (a < b)
		cout << "b is greater than a" << endl;
	if (b == c)
		cout << "b equals to c" << endl;
	return EXIT_SUCCESS;
}

std::array가 제공하는 메소드는 아래와 같다.

begin       컨테이너의 처음 반복자를 반환한다.
end         컨테이너의 마지막 반복자를 반환한다.
rbegin      컨테이너를 거꾸로 순회하는 첫 반복자를 반환한다.
rend        컨테이너를 거꾸로 순회하는 마지막 반복자를 반환한다.
cbegin      c는 const의 뜻이다. write가 불가능한 처음 반복자를 반환한다.
cend        write가 불가능한 마지막 반복자를 반환한다.
crbegin     const + reverse + begin
crend       const + reverse + end

size        배열의 크기를 반환한다.
max_size    배열의 최대 크기를 반환한다.
            (vector와 다르게 size 함수와 항상 동일하다)
empty       배열이 비어 있으면 `참` 아니면 `거짓`

operator[]  원소 접근
at          원소 접근(out_of_range 예외 던짐)
front       배열의 첫 원소 반환
back        배열의 마지막 원소 반환
data        배열의 주소 반환

fill        배열을 채우는 메소드
swap        두 컨테이너의 값을 바꾸는 메소드

가능하다면 C 스타일의 배열보단 C++11에서 제공하는 std::array를 사용하자.

3. auto키워드가 함수의 반환??

함수의 반환형도 auto로 사용할 수 있다. 물론 아무데서나 사용할 수 있긴 하지만. 무조건 int를 반환하는데 auto로 사용할 필요는 없다.

또한 C++11 ( Visual Studio 2013 , g++ 4.x )에서는 C++14를 사용할 수 없으므로 auto가 반환형인 함수에 대해서는 후행 반환 형식 을 기술하여야 한다.

그렇다 C++은 항상 template이 모든 문제를 야기해 왔다. 앞으로도 그럴것이다.

template과 auto는 같이 사용하면, 먼저 컴파일러가 template으로 사용되는 타입에 대한 [클래스, 함수] 등을 생성한다.

그런데 그런 함수의 반환형으로 auto를 사용하면 해당 템플릿을 추론한뒤, 그 템플릿함수의 반환형까지 추론해야 하기 때문에 컴파일러는 두번을 추론해야 한다.

C++11 컴파일러는 이럴 능력이 안되는데, 따라서 한번더 추론하는 키워드를 넣어줘야한다.

이것이 바로 decltype 이다. 말을 이렇게 했지만 C++11에서 함수의 반환형이 auto이면 무조건 후행반환 형식을 지정해야 한다.

반면 C++14에서는 auto만 써도 타입을 추론한다.

그러나 C++11호환을 위해 후행 반환 형식을 쓰는건 나쁘지 않아 보인다.

decltypedeclension type 의 약어이다.

#include<iostream>		//std::cout , std::endl
#include<cstdlib>		//EXIT_SUCCESS
#include<array>
using namespace std;
template<typename T>
auto Add(T a, T b)->decltype(a+b){
	return a + b;
}

template<typename Container,typename Index>
auto Nth(Container container, Index index)->decltype(container[index]) {
	return container[index];
}
int main(){
	array<int,5> arr = { 2, 5, 1, 3, 4 };
	cout << Nth(arr, 1) << endl;
	cout << Add(2.4, 3.2) << endl;
	return EXIT_SUCCESS;
}

Add함수의 경우 두 변수를 더하는데, 자료형에 구애받지 않는다. decltype은 변수이름을 인수로 받는다.

Nth함수도 해당 컨테이너의 원소의 타입은 container[index] 의 타입이므로 이 예시는 가장 적절하다.

또는 제네릭한 프로그래밍을 할때 auto와 함께 자주 쓰인다.

auto 는 R-value의 식으로 자료형이 결정 나지만, decltype 은 변수명으로 타입을 추론하기 때문에 사용예시가 다르다.

decltype 역시 참조와 포인터를 지정가능하다.