c++ 11

2021. 4. 11. 03:52 기타/C++

 C++ 11특징

 

- C++ 11 은 2011년 8월 12일 ISO c++ 표준 위원회의 만장 일치로 통과 되었습니다. 표준으로 인정받은 해에 맞춰 C++ 11이라는 명칭으로 정해졌습니다.

 

- 안정성 및 C++98과의 호환성을 유지 합니다.

- 이전의 안전하지 않은 기술에 대해 좀 더 안전한 대안을 제공 합니다.

- "부담 최소화"의 원칙. 어떤 유틸리티가 필요로하는 추가적인 자원은 그 유틸리티를 사용할 때만 필요해야 합니다.

- 문법의 편의성이 크게 향상 되었습니다.

템플릿에서 가변 인자

새로운 문자열 리터럴

사용자 정의 리터럴

멀티태스킹 메모리 모델

TLS (Thread Local Storage)

특수 멤버 함수의 기본값 사용 및 삭제에 대한 명시적 표시

long long int 타입

정적 assertion

멤버에 대한 sizeof 허용

 

• auto 키워드

C++11에서 auto 키워드는 컴파일러에게 타입을 알아내라고 지시하는 의미 입니다. 직접 타입을 지정하지 않아도 컴파일러가 자동으로 변수의 타입을 지정 합니다.

 auto ivalue = 1;       // ivalue의 타입은 int

 auto lvalue = 1L;     // lvalue의 타입은 long

 auto p = "문자열";   // p 변수 타입은 char Pointer

코드의 양을 상당히 줄여주는데 유용합니다.

#include <iostream>
#include <vector>
using namespace std;

int main(int argc, char** argv)
{
    vector<int> vInt;
    for(auto i=0; i < 10; ++i) {
        vInt.push_back(i);
    }

    // C++03 표준
    cout<<"C++03 standard"<<endl;


    vector<int>::iterator it = vInt.begin();


    while(it != vInt.end()) {
        cout<<*it<<endl;
        it++;
    }

    // C++11 표준
    cout<<"C++11 standard"<<endl;


    auto it2 = vInt.begin();


    while(it2 != vInt.end()) {
        cout<<*it2<<endl;
        it2++;
    }
    getchar();
    return 0 ;
}

• 범위기반 for 문

- for 루프가 간단해 졌습니다.

- 루트의 순회값(i)의 초기값을 정해줄 필요가 없습니다.

- 배열의 길이를 지정해 주지 않아도 됩니다.

- 언제까지 순회해야 할지 지정할 필요가 없습니다.

int arr[5] = {0, 1, 2, 3, 4};
for(int i: arr) {
    std::cout<<i<<std::endl;
}



//C++03
std::vector<int>::const_iterator it;
for(it = v.begin(); it != v.end(); ++it) {
    std::cout << *it << std::endl;
}

// C++11
for(auto i: v) {
    std::cout << i <<std::endl;
}

• 유니폼 초기화

 //c++03
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);

//c++11
vector<int> v2 {1, 2, 3, 4};
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;

class Person {
    private:
    int age;
    string name;

    public:
    Person(const int a, const string& n):age(a), name(n){ }
    int getAge() { return age; }
    string getName() { return name; }
};

int main(int argc, char **argv) {
    // c++03
    Person p1{20, "Tom"};
    Person p2{19, "Jane"};

    // c++11 유니폼 초기화
    vector<Person> vec {
        {21, "Smith"}, {39, "John"}, {23, "Mary"}, {45, "Ted"}
    };

    for_each(vec.begin(), vec.end(), [](Person p) {
        cout<<p.getAge()<<", "<<p.getName()<<endl;
    });
    return 0;
}

 

• decltype 키워드

decltype키워드는 주어진 표현식의 타입을 컴파일러가 직접 추론해서 결정하라고 지시하는데 사용합니다. auto 키워드와 비슷하나, decltype키워드는 주로 함수의 반환타입을 결정하는데 사용 됩니다.

auto가 값에 상응하는 타입을 추론시켜주는 키워드라면,
decaltype은 값으로부터 타입을 추출해 낼 수 있는 키워드라고 생각하면 됩니다.

decltype은 주로 주어진 템플릿 인자에 기반한 generic programming의 어려움을 해소하기 위해 도입되었습니다.

 int int add1(int a, int b) {
    typedef int TYPE_ADD_1;
    TYPE_ADD_1 c = a + b;
    return c;
}

int add2(int a, int b) {
    typedef decltype(a+b) TYPE_ADD_2;
    TYPE_ADD_2 c = a + b;
    return c;
}



auto add3(int a, int b) {
    return a + b;   // 컴파일러가 반환타입을 알 수 없어 Error!!!
}



auto add4(int a, int b) -> int {
    return a + b;
}

전달되는 파라미터와 상관 없이 반환 타입이 정해지는 상황인 경우

#include <string>
#include <iostream>
using namespace std;

// auto와 decltype 키워드를 이용한 템플릿 함수
template<typename T, typename F>
auto execute(const T& value, F func) -> decltype(func(value)) {
    return func(value);
}

// 정수 타입 숫자를 해당 문자로 변경하는 함수
string number2String(const int i) {
    switch(i) {
        case 1:
            return "one";
        case 2:
            return "two";
        case 3:
            return "three";
        case 4:
            return "four";
        default:
            return "unknown";
    }
}

// 문자 타입 숫자를 해당 정수로 변경하는 함수
int string2Number(const string& str) {
    if(!str.compare("one"))
        return 1;
    else if(!str.compare("two"))
        return 2;
    else if(!str.compare("three"))
        return 3;
    else if(!str.compare("four"))
        return 4;
    else
        return -1;
}


int main(int argc, char** argv)
{
    //
    cout <<execute(3, number2String) <<endl;
    cout <<execute("three", string2Number) <<endl;

    //
    cout << "execute(3, number2String) has return type :\n"
        <<typeid(execute(3, number2String)).name() <<endl;
    cout << "execute(\"three\", string2Number has return type :\n"
        <<typeid(execute("three", string2Number)).name() <<endl;
    return 0;
}

 

decltype은 템플릿 함수를 동반하는 제너릭generic 코드를 작성할 때는 기존에 굉장히 어렵거나 구현이 거의 불가능했던 로직을 간단하게 구현할 수 있습니다. 이것이 decltype 키워드가 새롭게 추가된 이유입니다.

 

C++11(VS2013)부터 auto 타입 반환이 가능해지고 trailing return type(후행 반환 형식)으로 decltype을 사용함으로써, 템플릿 함수의 auto 반환이 상당히 유연해지고 자유로워 졌습니다.

 

 // auto 반환함수와 후행 반환 형식으로 int 사용
auto add_function(int a, int b) -> int
{
    return a + b;
}

// auto 반환과 후행 반환 형식으로 decltype()을 사용
template <typename T, typename U>
auto add_template(T&& x, U&& y) -> decltype(std::forward<T>(x) + std::forward<U>(y))
{
    return std::forward<T>(x) + std::forward<U>(y);   
}

// BUILDER의 makeObject() 반환 형식으로부터 자유로워짐
template <typename TBuilder>
auto MakeAndProcessObject(const TBuilder& builder) -> decltype(builder.makeObject())
{
    auto val = builder.makeObject();
    // process...
    return val;
}

C++11에서 auto 반환 함수는 반드시 후행 반환 형식을 지정해 주어야 하며, 특히 템플릿 함수들의 경우 타입을 템플릿 인자들로부터 추론해야 하므로 decltype을 활용하지 않으면, 컴파일 단계에서 auto 반환 형식을 재대로 추론하지 못해 컴파일 에러가 발생하게 됩니다.

 

 error: 'add_template' function uses 'auto' type specifier without trailing return type
auto add_template(T x, U y)// -> decltype(x + y)

 

C++14(VS2015)부터는 auto 반환시 후행 반환 형식을 지정해 주지 않아도 문제 없이 반환 타입을 추론해 준다. 즉, C++11에서 그랬듯 일일히 후행 반환 형식을 써주지 않아도 됩니다.

 

#include <iostream>
#include <string>

template <typename T, typename U> 
auto add_template(T&& x, U&& y) // -> decltype(std::forward<T>(x) + std::forward<U>(y))
{
    return std::forward<T>(x) + std::forward<U>(y);   
}

auto add_function(int a, int b) // -> decltype(a+b)
{
    return a + b;
}

template <typename TBuilder>
auto MakeAndProcessObject(const TBuilder& builder)      // -> decltype(builder.makeObject())
{
    auto val = builder.makeObject();
    // process...
    return val;
}

struct Builder
{
    static std::string makeObject() { return std::string("hello"); }
};

int main()
{
    add_template(1, 2);
    add_function(1, 2);

    // a is std::string type
    Builder builder;
    auto a = MakeAndProcessObject(builder);
}

• std::Array

벡터와 같이 정해진 시간에 임의 접근이 가능하며, 메모리의 스택에 연속적으로 요소들을 배치할 수 있습니다. 보통 벡터는 자유 공간에 요소들을 비연속적으로 배치 하여 사용 합니다. 그런데 std::Arrays는 요소들이 메모리 안 특정 지역에 모여 있도록 배치해, 해당 요소들을 좀 더 빠르게 처리할 수 있습니다. std::array는 생성자, 소멸자, 복사 생성자, 대입 연산자를 지원합니다.

 

 #include <array>
int main(int argc, char** argv) {
    std::array<int, 5> arr = {1, 2, 3};
    return 0;
}

 

초기화 리스트가 배열의 크기보다 작으면 0으로 자동 초기화 됩니다.

 

포인터 타입 변환

 int* p = arr; // 에러

int* p2 = arr.data(); // ok
const int* p3 = arr.data(); // ok

 

배열의 크기 : 멤버 함수 size()를 통해 알아낼 수 있습니다.

std::cout<<arr.size()<<std::endl; // 출력 값 5

부모 타입으로 타입 변환 금지

유효한 케이스

#include <iostream>
#include <array>
//...
std::array<int, 5> arr = {1, 2, 3};

//...
auto it = arr.begin();

// 위 코드는 std::array<int. 5>::iterator it = arr.begin()과 같음
while(it != arr.end()) {
    std::cout<<*it<<std::endl;
    it++;
}

// ...
std::array<int, 5>::size_type size = arr.size();

arr.fill(12); // 모든 배열 원소의 값을 12로 할당

bool isEmpty = arr.empty();

// 벡터에서 지원되는 멤버 함수(operator[ ], at, front, back)도 지원합니다.

std::array와 std::vector의 비교

- std::array는 크기가 고정되어 있으며 std::vector는 동적으로 증가 합니다.

- std::array는 동적 메모리를 사용하지 않지만 std::vector는 동적 메모리를 사용합니다.

 

std::array와 C-Array의 비교

- std::array는 객체이므로 크기를 알 수 있지만 C-Array는 크기를 알 수 없습니다.

- std::array는 요소가 하나도 없어도 되지만 C-Array는 하나 이상 있어야 합니다.

- std::array는 생성시 파라미터를 통해 명시적으로 크기를 알려줘야 하지만, C-Array는 초기화를 통해 크기를 추측해낼 수 있습니다.

- std::array는 대입이 가능하지만 C-Array는 불가능합니다.

 

출처 : code-factory.tistory.com/16?category=726135