본문 바로가기

Programmer.cpp/Cplusplus.cpp

C++ 버전별 변천사 #3 - C++11

이번에 소개할 버전은 C++11입니다.

한 가지 사과의 말씀을 드려야할 것이 C++11을 정리하려다보니 내가 여지껏 C++0x라는 명칭을 잘못 사용하고 있었더군요. C++0x는 C++11이 원래 2008년에 표준화될 계획이어서 붙여졌던 이름인데 2011년에 C++11이 표준화되면서 지금은 쓰이지 않는 이름이라고 합니다. (제가 C++03을 한참 사용하던 시절에 자주 듣던 이름이어서 혼동을...) 이전의 문서에서는 모두 03으로 오류를 수정해 두었으니 글 읽으시는 분들도 오해 없으시기 바랍니다.

한 가지 더. STL 관련된 부분은 너무 많고, 버전별로 수정된 사항도 있어서 나중에 C++03부터 14까지 한꺼번에 정리를 할 예정입니다. (그래서, 이번 글에서 C++11의 STL 관련부분은 이름만 대충 나열할 참입니다)



C++11은 정말 많은 변화가 있는 버전이다. 2014년에 C++14가 표준화되기 전까지 최신버전이었다. C++14는 아직 제대로 지원되는 컴파일러가 일부 뿐이라는 점을 감안한다면 현실적인 최신버전이라고 봐도 무방하다.

이제 C++11 버전부터는 사용할 수 있는 컴파일러 버전도 체크할 필요가 있으므로, 이 쯤에서 C++11 이 후의 버전들을 사용할 수 있는 컴파일러들을 한 번 살펴보고 넘어가자.


C++11 

GCC 4.8.1 (4.8 버전에서도 대부분 기능을 지원한다)

Visual C++ 2013에서 일부 기능을 제외하고 지원 

Intel C++ Compiler 14.0

Clang 3.3

XCode 4.6 ( Apple Clang 4.2 )

C++14

GCC 5.0에서 지원

Visual C++ 2013 부터 일부 지원. 2015에서도 일부기능 미지원(예정)

Intel C++ Compiler 15.0 에서 일부 지원

Clang 3.4

XCode 5.0 ( Apple Clang 5.0 )


필자가 애용하는 Visual C++의 경우는 앞으로 지원되지 않는 내용에 대해서는 가급적이면 언급하고 넘어가도록 할 예정이다.


이제 본격적으로 C++11에 새로 추가되거나 수정된 부분들을 살펴보겠다.


auto 타입

auto 타입은 초기화 과정에서 타입이 결정된다. 그 때 그 때 타입을 정하면 되지 라고 생각할지 모르겠지만, 이거 써보면 정말 편하다. 주로, 두 가지 경우에 많이 사용되는데

- 타입 이름이 정~말 길 때 유용하다. STL을 쓰다보면 이런 코드 많이들 써봤을거다


for( vector<UserType<int>>::iterator it = ut.begin(); it != ut.end(); ++it )


이걸 auto를 이용하면


for( auto it = ut.begin(); it != ut.end(); ++it )


이렇게 줄일 수 있다. (C++11에서는 한 번 더 줄여줄 수 있지만, 좀 있다가 이야기하겠다)

- 또 한 가지 경우는 그냥 이거다. 타입따위 생각하고 싶지 않을 때... 그냥 막 쓰면 된다. 특히 생전 첨 보는 함수를 쓸 때 return 타입을 신경쓰고 싶지 않을 때 써먹기 좋다. 특히, 아래에서 언급할 lambda expression을 쓸 경우에 좋다.


decltype

auto를 사용하기 위해서는 반드시 초기화를 동반해야한다. 그런데, 초기화는 천천히 하고, 일단 변수부터 선언하고 싶을때 이 녀석을 사용하면 된다.

auto a1 = AnyObject;        // 반드시 초기화도 함께 해야하지만

decltype( AnyObject ) a2;    // 이렇게 선언하고

a2 = AnyObject;            // 나중에 써먹는게 가능해진다.

특히나, 초기화를 동반하기 힘든 template class의 타입지정에 유용하다.

vector<decltype(AnyObject)> va3;    // 이걸 초기화랑 함께 쓸 수는 없잖소~


range-for statement

위에서 언급한 for문을 한 번 더 줄이는 방법이 이거다.

for( auto& i : ut )

이걸 첨 보는 사람은 무지 생소할 수 있다. 두 가지 경우에 사용이 가능한데

- 크기가 확정된 배열

- begin, end 가 제공되는 container

이 경우 배열이나 container를 처음부터 끝 까지 순회할 수 있다.


nullptr

이게 왜 이제서야 만들어졌는지 알 수 없지만, 여하튼.. 이제서라도 나왔으니 열심히 써주자.

더이상 #define NULL 0 은 쓸 일이 없다.

타입도 포인터로 한정되기 때문에 포인터에 0을 집어넣다가 실수로 이상한 값을 넣게 되는 일도 줄여줄 수 있다.


final

C++에도 드디어 final이 생겼다. 더 이상 계승할 수 없는 클래스를 꼼수 안쓰고 만들 수 있게 되었다. (이전까지는 private와 friend를 꼬아서 쓰는 꼼수로만 가능했다)

class C final { /* ... */ }; // 이렇게 쓰면 C로부터 계승받는 클래스를 만들 수 없다.

클래스 뿐 아니라 virtual function도 final로 지정해서 더 이상 override 할 수 없게 지정해줄 수 있다.

virtual void f() final; // 대충 이런 식으로 쓴다.


Explicit override

virtual function을 선언할 때, base 클래스로부터 override 될 virtual function이 있는지 확인할 수 있는 키워드 override가 추가되었다. 클래스 계층이 복잡해지다보면 실수할 수 있는 실수방지용 문법이라 할 수 있다.

class C : B {

virtual void vf() override; // B 가 void vf() 라는 virtual function을 가지고 있을 경우에만 컴파일 된다.

};


rvalue reference

(lvalue와 rvalue가 무슨 말인지 모르겠다면 구글검색을 이용하자.)

간단히 설명하면 연산자의 왼쪽에 오는 값을 lvalue(Left-hand Value), 오른쪽에 오는 값을 rvalue( Right-hand Value)라고 하는데, lvalue는 특성상 메모리의 특정 영역을 할당받는 identifier를 가지는 반면, rvalue는 identifier가 없이 value만이 의미를 가진다.

기존의 C++03에서 사용되던 레퍼런스 타입은 엄밀히 말하면 lvalue에 대한 reference 였다. 즉, identifier를 가지지 않을 수도 있는 rvalue는 reference type으로 설정할 수가 없다는 소리다.

int& i = 3; // <== 이건 바로 에러다.

int&& ir = 3; // <== 이건 컴파일 된다!

이런 rvalue를 reference type으로 만드는것이 왜 필요할까 라는 자세한 이야기는 다음 기회로 미루자. (부연적으로 설명해야 할 사항들이 워낙 많아서 한 회 분량은 족히 된다) 다만, 새로이 C++11에 추가된 많은 부분들(move()라던가, perfect forward 라던가...이것들도 나중에 따로 소개하도록 하겠다)이 이것과 연관이 있다는 정도만 알아두기 바란다.


constexpr

변수, 함수, 클래스를 상수표현으로 사용할 수 있게 해주는 키워드인 constexpr이 제공된다.

constexpr int getnum(){ return 10; }
이렇게 선언해주면 배열의 크기라던가 case 문 등 상수만 사용할 수 있는 곳에서 getnum() 을 사용할 수 있게 된다.
반면, 상수로 쓰일 수 있기 때문에 제약사항이 있는데, 반드시 컴파일 타임에 상수로 계산될 수 있는 경우에만 사용할 수 있다. 얼핏 별 쓸모가 없어보이지만, 템플릿 메타프로그래밍을 이용하는 경우에 매우 유용하게 사용할 수 있다.

(이 키워드는 Visual C++ 2013에서 지원되지 않는다. 2015에서는 지원은 되지만 user defined type을 template class로 가지는 class template에 대해서는 여전히 지원되지 않는다고 한다.)


plain old data 규정 완화
plain old data(POD) 라는건 memcpy() 를 이용해서 복제가 가능한 data를 의미한다고 보면 된다. 즉, C스타일의 struct와 같은 형태를 생각하면 되는데, C++의 class도 일정한 규칙만 지키면 POD와 같이 memcpy를 이용해서 복제가 가능해지는 규정이 있다. C++에서는 이 규정이 살짝 완화가 되었고, 규정 완화를 위해 default constructor를 정의하는 문법이 추가되었다.
이 때문에 발생하는 문제점도 하나 있는데, POD가 아니면 union의 멤버로 추가할 수가 없기 때문에, 이 문제를 해결하기 위해 union에 새로 완화된 규정에 해당하는 POD를 추가할 경우 union에 해당 POD를 초기화할 수 있는 constructor를 추가해 주어야 한다. (이 부분은 Visual Studio의 경우 2015 버전에서 지원된다)

extern template
C++03에서는 template을 여러 파일에서 동시에 사용하기 위해서는 일일이 include해주고, 그러다보면 컴파일에 걸리는 시간이 엄청나게 증가할 가능성이 있었는데, C++11에서는 이를 방지하기 위해 template도 extern으로 선언할 수 있도록 개선되었다.

uniform initialization and initializer_list

C++03에서 class를 초기화해주기 위해서는 꽤 번거로웠던 기억들이 있을것이다. 특히, vector 같은 클래스를 하나하나 초기화해주는게 여간 귀찮은 일이 아니었다. C++11에서는 이러한 점을 개선하기 위해 새로운 초기화 문법과 initializer_list를 제공한다.

이제 아래와 같은 방식으로 초기화가 가능하다.

vector<int> v = { 1, 2, 3, 4 };

vector<int> v{ 1, 2, 3, 4 };

struct A {

 int i;

 string s;

};

A a{ 1, "test" };

void f(std::initializer_list<float> list);

f({1.0f, -3.45f, -0.4f});


lambda expression

C++11에서 가장 생소하게 느껴지는 부분이 아닐까 싶은 lambda expression이 추가되었다. 이것 역시 한 회 분량의 내용이니 간단히만 언급하겠는데, 기존의 함수 포인터를 이용할 때, 함수포인터를 vector 형태 등으로 묶음을 만들어 사용할 경우 일일이 함수를 정의하고, 각각의 이름을 지어서, 다시 그 함수들의 포인터를 vector에 집어넣는 불편함이 있었다면, 함수의 이름은 지정할 필요없이 lambda function들을 정의해서 vector에 집어넣을 수 있게 되었다.

[](int x, int y) { return x + y; }    // 대충 이런 형식으로 쓴다.


trailing-return-type

C++11에서는 함수의 return type을 지정하는 새로운 방법을 제공한다.

int f();

auto f() -> int;

이 두 문장은 똑같은 의미이다.

멀쩡하던걸 왜 새로 추가했느냐 하면... decltype을 이용한 타입을 return 타입으로 설정할 경우 발생 가능한 문제 때문인데

template<class Lhs, class Rhs>

  decltype(lhs+rhs) adding_func(const Lhs &lhs, const Rhs &rhs) {return lhs + rhs;} // 오류다!

이 경우에 decltype(lhs+rhs)에서 lhs와 rhs가 decltype을 적는 시점에서는 정의되지 않았기 때문에 부적절하기 때문이다. 따라서 이 경우는 trailing-return-type을 이용하여 아래와 같이 정의할 수 있다.

template<class Lhs, class Rhs>

  auto adding_func(const Lhs &lhs, const Rhs &rhs) -> decltype(lhs+rhs) {return lhs + rhs;}


construction 개선

이건 꽤 직관적으로 이해할 수 있는 개선사항이니 아래와 같은 문법들이 이제 가능해졌다 정도로 넘어가자.

class A {

 int i;

 int j = 5;    // 요놈 추가~

public:

 A( int n ) : i(n) {}

 A() : A(10) {}    // 요놈도 추가~

};


enumeration class

이제 enum 에도 특정 타입을 지정하고, 클래스화 할 수 있게 되었다.

enum class Enum : long { f1 = 1123, f2 = 44325l };

이런게 가능하다. 단, enum의 특성상 정수계열의 타입(int, short, unsigned, long 등)만 가능하다.

이게 class로 취급되기 때문에 어딘가에 정의만 되어 있다면 헤더파일에서 선언도 해줄 수 있다.

enum class Enum : long;    // 요런 식으로 써주면, Enum 이란 타입의 변수를 사용할 수 있게 된다.


right angle bracket

C++03에서는 template을 사용하다보면 쉬프트 연산자 >> 와 template에 사용되는 괄호(<, >)가 혼용되는 것을 막기 위해 > > 와 같이 사이에 공백을 반드시 넣어줘야 했었는데, C++11 부터는 이거 그냥 붙여써도 알아서 정리 잘 해주도록 개선되었다. 덕분에, 혹시라도 template class 정의할 때 >>를 쉬프트연산자로 사용할 경우(이런 경우는 왠만하면 쓰지말자 -_-)가 생긴다면 프로그래머가 직접 괄호 씌워가며 정리해줘야한다.


'using' template aliases

C++03에서는 template argument를 특정하여 typedef할 수 있는 방법이 없었다. C++11에서는 이런 경우 using 키워드를 이용해 typedef 대신 사용할 수 있다.

template <typename First, typename Second, int Third>

class SomeType;


template <typename Second>

using TypedefName = SomeType<OtherType, Second, 5>;


이런 경우 뿐 아니라 using으로 완전히 typedef를 대체할 수도 있다.

using UnsignedInteger = unsigned int;

using FunctionType = void (*)(double);


string literal extension

UTF나 Raw string을 위한 문자열 리터럴이 추가로 제공된다. (... 찍어놓은 부분이 문자열 값이다)

u8"..." // UTF-8

u"..." // UTF-16

U"..." // UTF-32

R"(...)" // Raw string

Raw string의 경우에는 따옴표(")와 괄호 사이에 16글자 이하의 특정 문자를 좌우에 동일하게 넣을 수도 있다. 예를 들면 R"delimiter(...)delimiter" R"123(...)123" 이런 식으로 사용이 가능하다.


user-defined literal

사용자가 임의로 정의한 리터럴을 사용할 수 있다. 리터럴을 문자열로 처리하는 operator "" 함수를 정의해서 특정 클래스에 사용될 리터럴까지도 정의해 줄 수 있다. 이것도 자세한 내용은 따로 소개하도록 하겠다.

(Visual Studio에서는 2015부터 지원한다)


thread 지원

C++11에서 어쩌면 가장 눈에 띄는 변화이기도 한데, thread를 지원하는 다양한 개선사항들(메모리 모델이라던지, thread class라던지)이 있다. 이것도 워낙 양이 많은 부분이니 오늘은 '지원한댄다' 정도로만...


explicitly deleted special member function

특정 타입의 argument를 명시적으로 사용하지 못하게 해주는 기능이 추가되었다.

예를 들어,

int member( double );

이렇게 멤버함수를 선언해 두면, member( 10 ) 과 같이 호출할 경우 10이 double 타입으로 형변환이 되어 member( double )이 호출되게 되는데

int member( int ) = delete;

이렇게 선언을 추가해주면, 이런 묵시적인 형변환을 통한 호출을 사전에 차단해 줄 수 있다.


long long int 타입 추가
64비트 int 타입인 long long int 타입이 추가되었다. (기존의 long int 타입은 컴파일러나 시스템에 따라 64비트 혹은 32비트 int 타입으로 사용되고 있었다)

static assertions
테스트 method인 static_assert가 추가되었다. 사용법은 구글링을 참고하기 바란다. 여기서는 문법만 소개하자면
static_assert (constant-expression, error-message); // 요렇게 쓴다~
예) static_assert(sizeof(int) <= sizeof(T), "T is not big enough!");

sizeof 기능 확장
sizeof가 class member에 대해서도 사용할 수 있도록 확장되었다. (사실, C++03에서 이게 안된다는 사실도 잘 몰랐다!!)
sizeof( A::a ); // 이제 이런것도 가능하다는 소리.
(Visual Studio는 2015부터 지원)

alignof, alignas
alignof는 특정 타입의 정렬기준이 되는 바이트 수를 얻어오는 기능을 하고, alignas는 특정 타입의 기준에 따라 특정 사이즈로 data alignment를 컨트롤해 줄 수 있다. 예를 들어
alignas(float) unsigned char c[sizeof(float)];
이렇게 선언하면, unsigned char 배열인 c를 float타입을 배치하는 간격에 맞추도록 한다. (즉, c[0]의 위치가 float타입이 정렬되는 기준(보통 4의 배수 번지)으로 배치된다)
(Visual Studio는 2015부터 정상지원된다. 2013버전은 일부지원)

Attributes
기존이 #pragma로 제공되던 컴파일러 확장명령을 위한 새로운 문법이 제공된다. C++11에서는 일단 [[noreturn]]과 [[carries_dependency]] 두 가지 attribute가 제공된다.
(하지만 Visual Studio는 지원 안한다. 그냥 #pragma 쓰자)


여기까지가 C++11에서 새로 추가된 사항들입니다. (아이고~ 많다)

나머지 부분들은 STL에서 개선된 부분이거나, 별도의 설명이 필요한 부분들이라 변천사에서는 생략을 하고, 별도의 포스팅으로 소개할 계획입니다. (날림으로 넘어가기도 애매한 내용들이 대부분이라...)
워낙 내용이 많아서 혹시나 빠뜨린 부분이 있을지도 모르니 누락되거나 수정이 필요한 부분은 제보해 주시기 바랍니다.


'Programmer.cpp > Cplusplus.cpp' 카테고리의 다른 글

C++ 버전별 변천사 #2 - C++03  (1) 2015.01.23
C++ 버전별 변천사 #1 - C++98  (0) 2015.01.20
C++ 이야기를 시작하면서  (0) 2015.01.18