C++11
C++에 손을 뗀지 어언 3년, 그동안 C++0x이니 C++11 같은 게 정말 좋아졌다고 하긴 하는데, 한눈에 알아볼 수 있는 한글 사이트가 없기에 한번 정리해봤다. 위키피디아 문서를 기준으로 요약, 번역한 것이니 틀린 점이 있으면 너그럽게 지적해주기 바란다.
아래 샘플 코드들을 Xcode 4.5에서 Apple LLVM 4.1
과 --std=c++11
, --stdlib=libc++
옵션으로 테스트 했음을 밝힌다.
요약
C++ 11 의 언어 레벨에서의 개선 사항은 대충 다음과 같이 요약할 수 있다.
키워드 | 설명 |
---|---|
&& | rvalue 참조 연산자 |
auto | 컴파일러가 유추해낼 수 있는 자동 타입. any 와는 다르다. |
char16_t, char_32_t | UTF-16 과 UTF-32 타입과 리터럴 선언 |
constexpr | 이름 그대로 상수 + 식 |
decltype | 변수 이름으로 타입을 알아내어서 선언 가능 |
noexcept | 어떤 함수가 예외를 던지는지의 여부를 지정 |
nullptr | NULL 포인터 상수 |
static_assert | assert 의 정적 버전 |
thread_local | 쓰레드 로컬 저장소를 사용하는 변수 지정자 |
using | type aliasing |
override | 가상 함수의 상속 여부 지정 |
alignas, alignof | 구조체나 클래스의 메모리 정렬 단위를 지정 |
final | 가상 함수나 클래스의 상속 불가능 여부 지정 |
표준 라이브러리 역시 많이 개선되었다. 그 중에서 핵심적인 부분들을 살펴보면,
클래스/함수 | 설명 |
---|---|
std::initializer_list | 초기화 리스트 |
std::thread, future, promise, async | 쓰레드 관련 클래스들 |
std::function | 함수 객체 |
std::regex | 정규식 |
std::unordered_map | O(1) 짜리 해시맵 |
std::tuple | 튜플 |
std::forward_list | 단방향 리스트 |
std::array | 배열 |
std::atomic | lockless 연산을 위한 클래스 |
rvalue 참조 연산자 &&
class A {
char* buf;
int size;
// move constructor
A(A&& a): buf(a.buf), size(a.size) { a.buf=0; a.size=0; }
};
주로 임시 객체가 만들어질 때 불필요한 메모리 할당이나 버퍼 복사로 인한 성능 저하를 막기 위해, 파라미터의 내부값 전체를 이동시켜 버리는 용도다. 많은 STL 컨테이너들이 성능을 위해서 이런 이동 생성자(move constructor)와 이동 대입연산자(move assignment operator)을 구현했다는데, 평범한 개발자가 쓸 일은 드물 듯.
더 많은 내용은 MSDN 참조.
constexpr
constexpr int get_five() {return 5;}
int some_value[get_five() + 7];
constexpr
을 붙이면 일반 변수나 함수도 컴파일 타임 상수로 쓸 수 있다. 단 함수일 경우 내부에 리턴문만 있어야 한다.
템플릿의 Extern 선언
extern template class std::vector<MyClass>;
이제 특수화된 템플릿 클래스의 extern 선언이 가능해졌다. 단 한 군데에서만 컴파일하게 되므로 컴파일 시간도 빨라졌다.
리스트 초기화
class SequenceClass {
public:
SequenceClass(std::initializer_list<int> list);
};
SequenceClass some_var = {1, 4, 5, 6};
void function_name(std::initializer_list<float> list);
function_name({1.0f, -3.45f, -0.4f});
std::vector<std::string> v = { "xyzzy", "plugh", "abracadabra" };
struct AltStruct {
AltStruct(int x, double y) : x_{x}, y_{y} {}
private:
int x_;
double y_;
};
AltStruct var2{2, 4.3};
새롭게 추가된 initializer_list
를 이용하면, 리스트로 객체를 초기화하거나 파라미터로도 넘길 수 있고, 생성자를 대체할 수도 있다. 맨 마지막 문법은 좀 손가락이 꼬이는 느낌.
auto 와 decltype
auto some_type = boost::bind(&some_function, _2, _1, some_object);
decltype(some_type) other_type = some_type;
템플릿 메타 프로그래밍에서는 리턴값을 알기 힘든 경우가 많은데, 이때 auto로 변수를 선언하면 컴파일러가 자동으로 타입을 찾아준다. decltype
은 변수만으로도 타입을 추론해낼 수 있다.
덕분에, iterator를 사용하는 for 루프는 정말 간단해질 수 있다.
for (std::vector<int>::const_iterator itr = myvec.cbegin(); itr != myvec.cend(); ++itr)
for (auto itr = myvec.cbegin(); itr != myvec.cend(); ++itr)
auto 를 리턴하는 함수의 경우, ->
를 이용해서 타입을 컴파일러에게 친절하게 알려줄 수 있다.
struct SomeStruct {
auto func_name(int x, int y) -> int;
};
auto SomeStruct::func_name(int x, int y) -> int {
return x + y;
}
범위 기반 for 루프
int my_array[5] = {1, 2, 3, 4, 5};
for (int &x : my_array) {
x *= 2;
}
배열, 초기화 리스트, begin/end가 정의된 컨테이너 등 컴파일러가 범위를 알 수 있을 듯한 컬렉션들에 한해서만 가능하다. in 연산자를 지원해주면 참 좋겠지만...
람다식과 람다 함수
[](int x, int y) { return x + y; }
함수명에 []
을 쓰면, 람다 함수로 사용할 수 있다. 리턴값은 자동적으로 추론된다고 한다.
생성자 관련 개선
class SomeType {
int number;
int value = 5;
public:
SomeType(int new_number) : number(new_number) {}
SomeType() : SomeType(42) {}
};
생성자에서 다른 생성자를 호출할 수 있게 되었고, 데이터 멤버도 클래스 선언시에 초기값을 가질 수 있다.
override & final
struct Base {
virtual void some_func(float);
};
struct Derived : Base {
virtual void some_func(int) override; // ill-formed because it doesn't override a base class method
};
struct Base1 final { };
struct Base2 {
virtual void f() final;
};
c# 이나 자바 같은 언어에서 오래전부터 지원하던 override 와 final 지정자가 드디어 표준이 되었다. final 은 클래스에 대해서도 사용 가능하다.
널 포인터 상수 nullptr
char *pc = nullptr; // OK
int *pi = nullptr; // OK
bool b = nullptr; // OK. b is false.
int i = nullptr; // error
foo(nullptr); // calls foo(char *), not foo(int);
NULL 이 0 과 혼용되면서 생긴 문제 때문에, 포인터에 대한 전용 상수인 nullptr이 추가되었다.
열거자의 타입 지정
enum class Enum2 : unsigned int {Val1, Val2};
예전 열거자는 컴파일러 구현에 따라서 int 도 되고, unsigned int 도 되었다. 새 열거자 선언 방식에서는 기본적으로 int이고, 별도로 타입을 지정할 수도 있지만, 정수로 암묵적으로 변환되지는 않는다.
using
template <typename First, typename Second, int Third>
class SomeType;
template <typename Second>
using TypedefName = SomeType<OtherType, Second, 5>;
typedef void (*Type)(double); // Old style
using OtherType = void (*)(double); // New introduced syntax
매크로나 typdef 대신 using 을 이용해서 긴 템플릿 이름을 짧게 줄일 수 있다.
가변 갯수 템플릿 인자
template<typename... Arguments>
void SampleFunction(Arguments... parameters);
SampleFunction<int, int>(16, 24);
세상에나, 템플릿 인자도 가변적으로 받을 수 있구나. 여기 참조.
union 개선
#include <new> // Required for placement 'new'.
//
struct Point {
Point() {}
Point(int x, int y): x_(x), y_(y) {}
int x_, y_;
};
//
union U {
int z;
double w;
Point p; // Illegal in C++03; legal in C++11.
//
// Due to the Point member, a constructor definition is now required.
//
U() {new(&p) Point();}
};
union 에 non-trivial 생성자를 가진 클래스를 담을 수 없던 제약이 풀렸다. 근데 이런 거 쓰는 사람이 있나.
유니코드 문자열 선언
const char* a = u8"I'm a UTF-8 string.";
const char16_t* b = u"This is a UTF-16 string.";
const char32_t* c = U"This is a UTF-32 string.";
이제, utf-8 문자열을 하드코딩할 수 있다.
const char* d = R"(The String Data \ Stuff " )";
const char* e = u8R"XXX(I'm a "raw UTF-8" string.)XXX";
const char16_t* f = uR"*(This is a "raw UTF-16" string.)*";
const char32_t* g = UR"(This is a "raw UTF-32" string.)";
정규식을 위한 raw 문자열도 추가되었다. 단, R 만 붙이는게 아니라 따옴표와 괄호까지가 필수적이라서, 첫번째 괄호와 마지막 괄호 내부의 문자열 escape 되지 않게 된다.
멤버 함수 삭제
struct NonCopyable {
NonCopyable & operator=(const NonCopyable&) = delete;
NonCopyable(const NonCopyable&) = delete;
NonCopyable() = default;
};
컴파일러가 자동으로 만들어주던 기본 생성자와 복사 생성자, 기본 소멸자 및 대입 연산자에 대해서 명시적으로 default 또는 deleted 를 지정할 수 있게 되었다. 물론 일반 멤버 함수에 대해서도 가능하다.
컴파일 시간 assertions
static_assert((GREEKPI > 3.14) && (GREEKPI < 3.15), "GREEKPI is inaccurate!");
template<class T>
struct Check {
static_assert(sizeof(int) <= sizeof(T), "T is not big enough!");
};
된다!
sizeof 개선
struct SomeType { OtherType member; };
sizeof(SomeType::member); //Does not work with C++03. Okay with C++11
데이터 멤버에 대한 sizeof
가 가능해졌다.
동적 메모리 관리(Garbage Collection)
컴파일러에 따라 동적 메모리를 자동으로 관리할 수 있게 되었다. 물론 수동으로 관리하는 방법도 제공한다.
쓰레드 관련
std::thread
클래스가 지원되고 다음과 같은 쓰레드간 동기화 프리미티브들이 포함되었다.
std::mutex
std::recursive_mutex
std::condition_variable
std::condition_variable_any
std::lock_guard
std::unique_lock
std::atomic<bool> ready(false);
std::atomic<int> data(0);
// Thread 0:
data.store(1, std::memory_order_release);
ready.store(true, std::memory_order_release);
// Thread 1:
if (ready.load(std::memory_order_acquire))
assert(data.load(std::memory_order_acquire) == 1);
보다 가벼운 원자적 연산(atomic operation)도 드디어 표준으로 채택되었다. 윈도우의 InterlockedIncrement()
류라고 보면 된다. 메모리 정렬은 좀 헷갈리니 여기를 참조.
std::packaged_task<int()> task([](){ return 7; }); // wrap the function
std::future<int> f1 = task.get_future(); // get a future
std::thread(std::move(task)).detach(); // launch on a thread
f1.wait();
std::cout << f1.get() << std::endl;
std::packaged_task
를 이용하면, 비동기로 함수를 실행하고, 그 결과가 끝날 때까지 대기할 수 있다.
std::future<int> f2 = std::async(std::launch::async, [](){ return 8; });
f2.wait();
std::cout << f2.get() << std::endl;
std::async
역시 future 를 리턴하지만, 자동으로 쓰레드를 만들고 함수를 실행해준다.
튜플
typedef std::tuple <int, double, long &, const char *> test_tuple;
long lengthy = 12;
test_tuple proof (18, 6.5, lengthy, "Ciao!");
lengthy = std::get<0>(proof); // Assign to 'lengthy' the value 18.
std::get<3>(proof) = " Beautiful!"; // Modify the tuple’s fourth element.
가변 인자 템플릿 덕분에 튜플의 인자 갯수 제한이 풀렸다.
unordered 해시 테이블
기존의 map 과 set 은 정렬된 트리를 사용하므로 검색에 O(log N)
이 걸리지만, unordered 는 메모리를 좀 더 쓰는 대신 O(1)
로 요소를 찾을 수 있다.
Type of hash table | Associated values | Equivalent keys |
---|---|---|
std::unordered_set | no | no |
std::unordered_multiset | no | yes |
std::unordered_map | yes | no |
std::unordered_multimap | yes | yes |
정규식
std::regex rgx(R"([ ,.\t\n;:])");
std::cmatch match;
if( std::regex_search( "Unseen University - Ankh-Morpork", match, rgx ) ) {
for( size_t a = 0; a < match.size(); a++ ) {
std::string str( match[a].first, match[a].second );
std::cout << str << "\n";
}
}
C++ 에서 정규식이 된다니 감개가 무량하다...
스마트 포인터
std::auto_ptr
은 사라지고, std::unique_ptr
, std::shared_ptr
그리고 and std::weak_ptr
이 표준에 합류했다.
ref
void func (int &r) { r++; }
template<class F, class P> void g (F f, P t) { f(t); }
int i = 0 ;
g (func, i) ;
std::cout << i << std::endl; // Output -> 0
g (func, std::ref(i));
std::cout << i << std::endl; // Output -> 1
임의의 객체의 레퍼런스를 얻어내는 ref
와 cref
가 추가되었다.
std::function
struct Foo {
Foo(int num) : num_(num) {}
void print_add(int i) const { std::cout << num_+i << '\n'; }
int num_;
};
void print_num(int i)
{
std::cout << i << '\n';
}
std::function<void(int)> f1 = print_num;
std::function<void()> f2 = []() { print_num(42); };
std::function<void()> f3 = std::bind(print_num, 31337);
std::function<void(const Foo&, int)> f4 = &Foo::print_add;
Foo foo(314159);
f4(foo, 1);
함수 객체는 일반적인 함수 포인터 뿐만 아니라, 람다 함수, 멤버 함수, bind식까지 담을 수 있다.
기타
- 템플릿 선언할 때 일부러
> >
띄워쓰지 않아도 컴파일러가 잘 처리해준다. - 변환 연산자(conversion operator)에도
explicit
를 붙일 수 있다. thread_local
지시를 붙이면 쓰레드 로컬 변수를 선언할 수 있다.