[book] C++11 주요 변경사항

  • C++11 STL 프로그래밍 책을 기반으로 정리함.

C++11 기초

auto

  • 컴파일 시점에 형을 정한다.
  • 지역변수에만 사용할수있고 클래스 멤버변수나 전역변수, 함수의 인자로는 사용할 수 없다.
  • 아래의 예시처럼 사용 가능한다.
int test()
{
    //char*
    auto test_char = "test string";
    //int
    auto test_number = 1;
    //포인터, 참조, const 사용
    int usrMode = 4;
    auto* pUsrMode = &usrMode;
    auto& refUsrMode = usrMode;
    refUsrMode = 5
    //아래처럼 간편하게도 가능하다
    typedef std::list<Custom*> Custom_list;
    Custom_list::iterator iter = custom.begin();
    //대신에 아래처럼
    auto iter = custom.begin();
    
}

람다

  • lamda는 람다함수, 이름없는 함수로 부른다.

  • 람다의 기본 문법

int main()
{
    []  //lamda capture
    ()  //함수의 인수 정의
    {}  //함수의 본체
    (); //함수 호출
}
  • 사용 예제
//간단 예제
int main()
{
    []{std::cout << "hello" << std::endl;}();
}

//auto를 사용하면 변수에 대입할수 있다.
auto func = []{std::cout << "hello" << std::endl;}();
func();

//일반 함수처럼 파라미너트를 정의 할 수 있다.
auto func = [](int n){std::cout << "hello" << n <<std::endl;}();
func(333);
func(777);

  • 캡쳐
    • 람다를 사용할 때 외부에 정의되어있는 변수를 람다 내부에서 사용하고 싶으면 그 변수를 capture 하면 된다.
      • 참조나 복사로 전달이 가능하다
        • 참조로 전달할때는 & 를 복사로 전달할 때는 변수이름을 기술한다.
    • 람다 표현의 [ ] ( 파라미터 ) { 식 } 에서 앞의 [ ] 사이에 캡쳐할 변수를 기술한다.
    • 만약 복사로 캡처한 변수를 람다 내부에서 변경해야 한다면 mutable 키워드를 사용하자.
      • 람다 내부에서 변경한 외부 변수의 값은 람다를 벗어나면 람다 내부에서 변경하기 전의 원래 값이 된다.
    • 복수의 변수 캡쳐
      • [ ] 사이에 캡쳐할 변수를 선언하면 된다.
        • [ &num1, & num2 ]
      • [ & ] 로 하면 어떻게 될까?
        • 람다 식을 정의한 범위 내에 있는 모든 변수를 캡쳐할 수 있다.
      • [ = ] 를 사용하면
        • 람다 외부의 모든 변수를 복사하여 캡쳐한다.
  • 예시
int main()
{
    int n1, n2, n3, n4, n5;
    
    [&, n1, n2] {};     //n3, n4, n5 는 참조, n1, n2는 복사
    [=, &n1, &m2] {};   //n3, n4, n5 는 복사, n1, n2는 참조
    [n1, n2]{};         //Error
    [&, &n1]{};         //Error 이미 default 로 사용중
    [=, n2]{};          //Error 이미 default 로 사용중
}

range based for

  • 반복문을 쉽고 안전하게 사용하기 위함
  • 기본적으로 STL 의 iterator를 지원하는 컨테이너라면 사용할 수 있다.
  • 표준 문법
for(for-range-declaration : expression) statement
  • 예제로 쉽게 알아보자
int num[5] = {1,2,3,4,5};

//일반적인 for문
for(int i=0; i<5;i++)
{
    std::cout<<i<<std::endl;
}

//range based for
for(auto i : num)
{
    std::count << i << std::endl;
}

//ex2
std::vector<int> numVector;
numVector.push_back(1);
numVector.push_back(2);
numVector.push_back(3);
for(auto i : numVector)
{
    std::count << i << std::endl;
}

//아래처럼 하면 i의 값을 for문안에서 변경할수 있지만 for문 밖으로 나오면 numBector 의 요소에는 적용되지 않는다. 
for(auto i : numVector)

//요소의 값을 변경하고 싶다면 참조를 사용한다
for(auto &i : numVector)

//for문에서 요소 값을 변경하지 못하게 하려면 const를 붙이자
for(auto const i : numVector)


//for문에서 데이터셋 요소에 접근할때 임시 변수를 만드므로 이 비용을 줄이고 싶다면 참조를 사용하는게 좋다. 
//만약 요소 값을 변경하지 못하게 하고 싶다면 아래처럼. 
for(auto const &i : numVector)

enum

  • enum 은 이미 C++에서 사용하고 있지만 C++11 에서는 C++03과 달리
    • unscoped enumeration 과 scoped enumeration 두종류의 enum으로 바뀌었다.
  • unscoped enumeration
    • 기존 enum과 비슷하다.
    • 예제
enum ITEMTYPE : short
{
    WEAPON,
    EQUIPMENT,
    GEM = 10,
    DEFFENSE,
};

//사용은 이렇게
short ItemType = WEAPON;
short ItemType = ITEMTYPE::WEAPON; //C++03에서는 에러 .
  • scoped enumeration
enum class CHARTER_CLASS : short
{
    WARRIOR = 1,
    MONK,
    FIGHTER,
};
//사용은 아래처럼
CHARTER_CLASS charClass = CHARTER_CLASS::WARRIOR;
//이렇게 하면 에러
shrot CharClassType = FIGHTER;//에러
  • enum class 대신 enum struct 를 사용해도 되고 형을 지정하지 않으면 기본적으로 int 형이 된다.
  • 형변환
//unscoped enumeration 은 기존과 같이 암묵적으로 정수로 변환할수 있다.
int i = WEAPON;
//그러나 scoped enumeration 은 명시적으로 타입캐스팅을 해야 한다.
int i = static_cast<int>(CHARTER_CLASS::WARRIOR);

nullptr

  • null pointer를 뜻한다.
  • C++03까지는 NULL 매크로나 상수 0을 사용하였는데, int형으로 추론되는 문제가 발생할수도 있었다.
  • 이전에 NULL 이나 0을 사용하는것 대신 nullptr을 쓰면 된다.

메모리 관리

  • C++03 에서 auto_ptr 이라는 smart pointer 가 있었는데, 제약이 있어서 많이들 사용하지 않는다.
  • 이를 해결하기 위해 나온 것이 shared_ptr, shared_ptr 이다.
  • 성능 측정을 해보아도 별 차이가 나지 않는다

    shared_ptr

//배열로 동적 할당 :  new Particle[2]
//삭제자 : std::default_delete
//삭제할 객체가 배열임을 알려준다 : Particle[]
std::shared_ptr<Particle> ParticlePtr(new Particle[2], std::default_delete<Particle[]>());
int main()
{
    std::shared_ptr<Particle> Particle1(new Particle(1));//Particle 생성하면서 참조수 1
    {
        std::shared_ptr<Particle> Particle2 = Particle;//Particle은 Particle2에 참조되면서 참조수 2
    }
    //스코프를 벗어나면서 Particle1을 참조하는 Particl2 가 파괴되어 Particl1의 참조수는 1
}
//main 스코프를 벗어나면서 Particle1 이 파괴되어 참조수는 0이 되고 Particle 클래스의 소멸자가 호출
  • shared_ptr은 생성될 때 참조수가 1이 되고 이 객체를 다른 곳에서 참조할때마다 참조수를 1씩 증가시킨다
  • 소멸자가 호출될 때마다 참조 카운트를 -1씩 감소 시켜서 참조수가 0이 될때 관리하고 있는 객체를 사용하지 않는것으로 판단하고 파괴한다.
  • shared_ptr 로 관리하는 객체의 포인터를 얻을때에는 get(), 참조를 얻을때에는 operator * , 인스턴스의 멤버에 접근할 때는 operator -> 를 사용한다.
  • shared_ptr의 reset() 멤버 변수를 사용하면 기존에 관리하던 객체를 다른 객체로 교체할수 있다.(기존 객체는 파괴된다. )
  • reset() 멤버 함수에 인자값을 사용하지 않으면 명시적으로 객체를 파괴할 수 있다.
  • 위에서, shared_ptr 를 정의할 때 std::default_delete<Particle[]> 로 삭제자를 지정했었는데, 그런데 함수나 함수 객체를 삭제자로 등록하면 shared_ptr 에서 관리하고있는 메모리를 해제할 때 특정한 행동을 할 수 있다.(memory pool 등을 사용할때 유용하다)
  • C++11의 auto와 std::make_shared 를 사용하면 간결하게 shared_ptr을 정의할 수 있다.
std::shared_ptr<Particle> ParticlePtr(new Particle(10));
// 이건 아래처럼 하면 된다.

auto ParticlePtr = std::make_shared<Particle>(10);

unique_ptr

  • shared_ptr 과 차이점은 객체를 독점적으로 관리한다는 것과 unique_ptr은 원천적으로 복사 생성자와 할당 연산자가 구현되어있지 않다. 즉 복사를 할수 없다 단지 이동(std::move) 만 할 수 있다.
std::unique_ptr<생성할 클래스 이름> 변수이름(new 생성할 클래스 생성자 호출);

std::unique_ptr<Prticle> ParticlePtr(new Particle(1));
  • 인스턴스의 포인터를 얻을 때에는 get, 참조를 얻을때엔 operator *, 인스턴스의 멤버에 접근할 땐 operator -> 를 사용한다.
  • 관리하는 메모리를 해제하고 싶을 때에는 release() 멤버 함수를 호출한다.
  • unique_ptr 은 삭제자를 지정하려면 템플릿 인자에 형을 선언해야 하는점이 shared_ptr과 다르다.

tuple

  • 이전에는 2개의 값을 하나로 묶을때 std::pair 를 사용했지만 이를 개선하여 2개 이상의 값을 하나로 묶을수있는 std::tuple 이 새로 생겼다.
  • 함수의 반환값으로 2개 이상의 값을 반환할때 사용가능하다.

    tuple 사용하기

  • 아래 샘플을 보자.
#include <tuple>

struct ITEM
{
    int nID;
}
int main()
{
    ITEM item;
    item.nID = 1000;
    std::tuple<int, std::string, ITEM UserItem = std::tuple<int, std::string, ITEM>>(1, "jack", item);
    
    //std::get<>으로 저장된 값을 사용할수 있다.
    std::get<0>(UserItem) = 2;
    std::cout << std::get<0>(UserItem) <<std::endl;
    //make_tupole 를 사용하면 간단하게 정의할 수 있다. 
    std::tuple<int, std::string , ITEM> UserItem2 = std::make_tuple(1, "tom", itme);
    //tuple 에 저장된 데이터 개수를 알고 싶을땐 std::tuple_size를 사용한다. 
    auto count = std::tuple_size<decltype(UserItem)>::value;
    //std::tie 유틸리티 함수를 사용하면 참조 데이터를 가지는 tuple를 만들거나 tuple 에 저장된 데이터를 일괄적으로 다른 변수에 담을수 있다. 
    int nUserIndex = 11;
    std::string userName = "nikc";
    auto refUserItem = std::tie(nUserIndex, userName);
    //tuple_cat 를 사용하면 tuple와 tuple를 합칠수 있다. 
    std::tuple<int, std::string> UserInfo(100, "jack");
    std::tuple<int, int> result(10, 7);
    auto UserResult = std::tuple_cat(UserInfo, result);
}

array

  • 고정길이 배열 자료구조를 사용하여 만들어진 array 컨테이터가 있다.
  • 다른 STL 컨테이너와 차이는 생성시 컨테이너의 크기를 지정해야하는 점이다.
#include <array>

//아래처럼 
std::array<int, 10> arr;

//생성과 동시에 초기화 할수 있다.
std::Array<int, 5> arr1 = {1,2,3,4,5};
//접근은 배열과 같이 operator []를 사용하여 임의접근이 되고, 반복자를 사용도 된다. 
for(int i=0; i<5; ++i)
{
    arr1[i] = i+1;
}
for(auto iter = arr1.begin(); iter != arr1.end(); ++iter)
{
    std::cout << *iter << std::endl;
}

//operator [] 나 반복자 외에 다른방법으로도 접근이 가능하다. 
arr1.at(2);//지정한 인덱스에접근
arr1.front();//첫번째 위치의 요소에 접근
arr1.back();//제일 마지막 위츠의 요소에 접ㅈ근
int* pData = arr1.data();//array 에서 보관중인 배열 데이터에 직접 접근한다. 
//array 크기를 알고 싶을때는 size를
//fill() 함수를 사용하면 array 의 모든 요소를 지정한 값으로 채울수 있다. 

forward_list

  • 기존 std::list 라이브러리가 있지만,이것은 양방향이다.
  • 단방향 리스트만으로 충분할 경우 사용한다.
  • 결국 list에 비해 메모리를 적게 사용한다.(단방향 리스트만 저장한다.)
  • list 보다 삽입 삭제 속도가 더 빠르지만 차이는 크지 않다.
  • 한 방향으로만 이동할수있다.
std::forward_list<int> flist;

//값 추가하기.
flist.push_front(1);
//지정한 위치 뒤에 새로운 데이터 추가하기
flist.insert_after(flist.begin(), 2);
flist.emplace_after(flist.begin(), 3);
//맨 앞에 새로운 요소를 추가한다.
flist.emplace_front(5);

//첫번째 위치의 데이터를 지운다.
flist.pop_front();
//지정한 위치 다음이나 지정된 위치 이후의 지정한 범위에 있는 모든 데이터를 지운다.
flist.erase_after(flist.begin(). flist.end());
  • 컨테이너에 데이터를 추가하는 함수의 이름 앞에 empliace_ 가 붙은것은 C++11에서 새로 생긴
    • Placement Insert 라는 기능이다.
//아래와 같은 경우는 ITEM 생성자에 의해 임시 객체를 생성하고 추가한다. 
flist.insert_after(flist.begin(), ITEM(3));
//하지만 아래는 가변인수 템플릿에 의해 생성자의 인수만 넘겨줘도 되고, 객체를 컨테이너에 한번만 생성하고 복사하지 않는다. 
// 그래서 큰 크기의 객체를 저장하는 컨테이너라면 Placement Insert 함수를 사용하는것이 좋다.
flist.emplace_after(flist.begin(), 4);
  • sort 함수를 사용하여 정렬할수 있다.
  • 정렬된 상태에서 unique 함수를 사용하면 같은 값을 가진 데이터를 제거할수 있다.
  • merge 와 splice_after 의 2가지 방법으로 컨테이너 2개를 합칠수 있다.
    • merger는 단순히 2개의 컨테이너의 앞과 뒤를 붙인다
    • splice_after는 위치와 범위까지 지정할수 있다.
  • list보다 불편하지만 C언어와 비슷한 성능을 내고 싶다면 사용하자.

unordered_map

  • C++11 이전의 hash_map과 거의 같은 컨테이너다.
#include<unordered_map>

std::unordered_map<int, std::string> map;
//추가.
map.insert(std::unordered_map<int, std::string>::value_type(1, "string1"));
//std::pair 로 간단히 추가
map.insert(std::pair<int, std::string>(2, "string2"));
//정방향 순회
for(auto iter = map.begin(); iter != map.rend(); ++iter)
{
    iter->second;
}
//역방향 순회
for(auto iter = map.rbegin(); iter != map.end(); ++iter)
{
    iter->second;
}
//검색 
auto findIter = map.find(1);
if(findIter == map.end())
{
}
else
{
    //있으면 삭제. 
    map.erase(1);
}
//모두 삭제
map.erase(map.begin(), map.end());

//저장된 요소중 key로 해당 요소가 있는 위치를 찾을때 lower_bound, upper_bound를 사용할 수 있다. 
//lower_bound 는 key 가 있는 위치의 반복자를 반환하고
// upper_bound 는 key가 있는 위치의 다음 위치에 있는 반복자를 반환한다. 
auto iter2 = map.lower_bound(3);
iter2 = map.upper_bound(3);

//emplace_hint 를 사용하면 검색할 때 찾는 것이 있으면 해당 요소가 있는 위치의 반복자를 반환하고
//없으면 새로운 값을 추가후 추가한 위치의 반복자를 반환한다. 
auto iter3 = map.emplace_hint(map.begin(), 3, "string3");
  • 기존 map 과 차이는 상식수준이라 굳이.

chrono

  • 예제를 보자
std::chrono::system_clock::time_point start = std::chrono::system_clock::now();//1970년 1월 1일

std::chrono::duration<double> sec = std::chrono::system_clock::now() - start;
//duration 클래스가 지원하는 시간단위
std::chrono::nanoseconds//나노 세컨드 10억분의 1초
std::chrono::microseconds//마이크로 세컨드 100만분의 1초
std::chrono::milliseconds//밀리 세컨드 1천분의 1초
std::chrono::seconds//초
std::chrono::minutes//분
std::chrono::hours//시

//시간 연산도 가능하다.
std::chrono::hours H1(1);//1시간
std::chrono::seconds S1(10);//10초
std::chrono::milliseconds MILS1(100);//100밀리세컨드

std::chrono::hours H1(1);
std::chrono::hours H1(2);
std::chrono::hours H3 = H1 + H2;

//system_colck 뿐 아니라 steady_clock 와 high_resolution_clock 도 있다. 

//to_time_t 는 system_clock::time_point 를 time_t로 변환한다
system_clock::time_point curTime = system_clock::now();
std::time_t = system_clock::to_time_t(curTime);

//from_time_t 은 time_t 를 system_clock::time_point 로 변환한다.
std::time_t t = time(NULL);
system_clock::time_point curTime = system_clock::from_time_t(t);

//high_resolution_clock 는 정밀도가 가장 높다. 

std::thread

  • thread 클래스의 생성자는 아래와 같다.
thread() _NOEXCEPT;

template<class Fn, class... Args>
    explicit thread(Fn&& F, Args&&... A);
thread(thread&& Othre) _NOEXCEPT;
  • Fn:(어플리케이션에서 정의한) 생성된 스레드가 호출할 함수
  • A : Fn 에 넘겨줄 인수 리스트
  • Other : 기존의 스레드 객체

  • thread가 종료될때까지 기다리려면 join 함수를 사용하면 된다.
  • get_id() 함수를 사용하면 스레드 클래스로 만들어진 스레드를 다른 스레드와 식별할 수 있다.
  • swap() 함수는 스레드 객체끼리 스레드를 교환할때 사용한다.
    • thread 식별자가 바뀐다.
  • detach 함수는 스레드 객체에 연결된 스레드 연결고리를 떼어낸다.
  • hardware_concurrency() 함수는 하드웨어상의 스레드 개수를 알려준다.
    • cpu 수, 코어 수, 하이퍼스레드 기능과 관련있다.
  • sleep_for 와 sleep_until 함수는 스레드를 일시 중지 시킬때 사용한다.
    • sleep_for는 특정시간 동안 일시 정지 (예. 2초간)
    • sleep_until 은 일정한 시간까지 중지(예. 12시 23분까지)
  • yield를 사용하면 스레드에 할당되어있는 시간을 포기하고 다른 실행 스레드에게 처리를 양도할 수 있다.

동기화 객체

  • 간단한 예제를 보자
std::mutex mtx_lock;
mtx_lock.lock();
...
mtx_lock.unlock();

//lock 은 이미 다른 스레드가 호출했으면 unlock 할때까지 대기를 해야한다 
//하지만 try_lock() 은 대기하지 않고 false를 반환한다. 

//mutex 를 lock_guard와 함께 사용하면 lock_guard 클래스가 생성될때 자동으로 lock을 호출하고 클래스가 소멸될때 자동으로 unlock을 호출한다.
std::mutex mtx_lock;
{
    std::lock_guard<std::mutex> guard(mtx_lock);
    ...
}
//lock_guard는 mutex 의 소유와 해제에 대한 RAII 패턴을 구현한것으로 볼수 있다. 

//timed_mutex 는 시간을 이용하여 mutex소유를 시도한다. 
//아래는 10초동안 소유권 요청을 시도하는 예이다.
if(mutex.try_lock_for(std::chrono::seconds(10)))
{
    ...
    mutex.unlock();
}

//unique_lock 은 lock_guard 클래스에 기능이 더해진 것이라 볼수있다. 
//lock_guard 는 정의와 동시에 락을 걸고 파괴될때만 락을 풀 수 있는데, unique_lock은 락을 정의와 동시에 걸수도 있고, 생성한 후에 걸수도 있다.
//락 풀기도 원하는 대로 조절할수 있으며 try_lock, try_lock_for, try_lock_until 등도 지원한다. 
//mutex를 세밀하게 사용하고 싶다면 unique_lock을 사용하자. 
std::mutex m;
//lock 생성과 동시에 mutex 소유권을 가진다.
{
    std::unique_lock lck(m, std::adopt_lock);
}
//lock k생성하지만 mutex 의 소유권은 가지지 않는다.
{
    std::unique_lock lck(m, std::defer_lock);
    ...
    lck.try_lock();
}

//복수의 mutex 객체를 동시에 락을 걸 수도 있다. 
std::lock(lockA, lockB);
//std::lock 함수의 정의는 아래와 같다.
template<class L1, class L2, class... L3>
    void lock(L1&, L2&, L3&...);
    
//unique_lock 과 함께 사용ㅇ하면 mutex를 좀더 안전하게 사용할 수 있다.


//멀티쓰레드 환경에서 딱 한번만 호출해야하는 경우가 있다면 std::call_once 를 사용하면 된다. 
//std::call_once 원형
template<class Callable, class... Args>
void call_once(once_flag& Flag, Callable F&&, Args&&... A);
//사용예제.
std::once_flag p_flag;
void create_instance()
{
}
void init()
{
    std::call_once(p_flag, create_instance);
    //...
}

//thread 마다 데이터 저장하기. 
//thread_local 은 각 스레드 고유의 스택 메모리에 데이터를 저장할수 있도록 한다. 
thread_local int nCount = 0;

//condition_variable 은 스레드간 특정 조건을 만족할 때까지 대기할 수 있도록 한다. 
std::mutex mtx_lock;
std::condition_variable cond;

//대기는 아래를 이용하고
wait, wait_for, wait_untile
//깨우는 것은 아래처럼 하자.
cond.notify_one();
cond.notift_all();

std::atomic

초기화와 읽고 쓰기

  • atomic_init 함수는 atomic 객체를 원하는 값(desired)으로 초기화한다.
    • 다만 이 함수는 lock free 하지 않다. 여러 쓰레드 간에 경합이 발생할 수 있다.
  • atomic_store 함수는 object 가 가리키는 값을 desired 로 락 프리하게 설정한다.
  • atomic_load 함수는 object 가 가리키는 값을 락 프리로 읽는다.
std::atomic<int> Num(0);
Num.load();

std::atomic_store(&Num, 2);
int result = std::atomic_load(&Num);

연산 조작

  • 더하기
    • atomic_fetch_add
  • 빼기
    • atomic_fetch_sub
  • AND 연산
    • atomic_fetch_and
  • OR 연산
    • atomic_fetch_or
  • XOR 연산
    • atomic_fetch_xor
std::atomic<int> Num(10);
int before = std::atomic_fetch_add(&Num, 7);
before = std::atomic_fetch_sub(&Num, 1);

int a = 0x0b;
int b = 0x0e;
std::bitset<4>(a).to_string();
std::atomic_fetch_and(&Num, b);
std::bitset<4>(Num).to_string();

바꾸기와 비교 후 바꾸기

  • 바꾸기
    • object 가 가리키는 값을 desired 로 바꾸고 변경 전의 값이 반환된다.
    • atomic_exchange
  • 비교 후 바꾸기
    • object 가 가리키는 값도 expected 가 같으면 true를 반환하고 object 가 가리키는 값을 desired 로 바꾼다.
    • 그러나 expected 와 다르면 false를 반환하고 expected 는 object 가 가리키는 값으로 바꾼다.
    • atomic_compare_exchange_weak
    • atomic_compare_exchange_strong

async/future

  • 비동기를 실행할 함수(함수객체, 람다 함수)와 함수에서 사용할 인자를 async 함수에 넘기고 반환되는 future 객체를 사용하여 비동기 함수 실행 결과를 받거나 완료 될 때까지 대기한다. 또한 policy를 지정하여 비동기 함수 실행 방법을 바꿀 수 있다.
std::future<> = std::async(실행방법(std::lauch::async or std::lauch::deferred, 비동기로 실행할 함수, 함수에 넘길 인자 (없으면 생략가능)));

std::future<int> future1 = std::async(std::lauch::async, GetMoneyFromDB, 10);

//std::lauch::async 를 사용하면 async 정의한 후 바로 비동기로 실행된다.
//std::lauch::deferred 를 사용하면 future 를 사용하여 결과를 대기할 때 실행된다. 

//아래 샘플 코드를 보자
int GetMoneyFromDB(int a)
{
    auto threadId = std::this_thread::get_id();
}
int main()
{
    auto threadId = std::this_thread::get_id();
    std::future<int> future1 = std::async(std::lauch::async, GetMoneyFromDB, 10);
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
    auto result1 = future1.get();
    ...
    std::future<int> future2 = std::async(std::lauch::deferred, GetMoneyFromDB, 20);
    std::this_thread::sleep_for(std::chrono::milliseconds(20));
    int result2 = future2.get();
}
  • std::promis 와 std::future 를 사용하면 thread 클래스에서 만든 스레드의 실행 결과를 얻을 수 있다.
    • thread에 인자값으로 promise 객체를 넘겨서 thread 의 실행 결과를 promise에 쓰고 future를 통하여 promise에 쓴 값을 얻는다.
void GetCharItmes(std::promise<std::vector<int>>& itemList)
{
    //...
}
int main()
{
    std::promise<std::vector<int>> itemList;
    std::future<std::vector<int>> f = itemList.get_future();
    
    auto t = std::thread(&GetChatItem, std::ref(itemList));
    t.detach();
    
    auto result = f.get();
}
  • std::packaged_task 클래스를 사용하면 간단하게 비동기 프로그래밍을 할 수 있다.
std::vector<int> GetCharItem(int a)
{
}
int main()
{
    std::packaged_task<std::vector<int>(int)> task(&GetCharItem);
    std::future<std::vector<int>> f = task.get_future();
    std::thread t(std::move(task), 11);
    
    t.detach();
    autu resutl = f.get();
}

string

  • string 의 기본 기능을 알아본다.
//문자열 사용없이 생성
std::string s1;
//문자열 사용
std::string s2 = "hello";
//문자열 일부만 사용 - 문자열의 2개만 복사
std::string s4("hello", 2);
//문자열 일부만 사용 - 문자열의 2번째 위치에서 8문자까지만 복사 
std::string s5("hello hello", 1, 8);
//반복자를 사용하여 문자열 복사
std::string s8(s2.begin(), s2.end());

  • resize() 함수를 사용하면 문자열 크기를 변경할 수 있다. 할당된 문자열의 크기를 변경하느 ㄴ것이지 메모리 영역의 크기를 변경하는 것은 아니다.
    • resize로 할당된 문자열 보다 크기를 작게 줄이면 크기만큼 문자열이 삭제됨
    • 늘리면 늘어난 크기만큼 빈 공백으로 채워짐.
  • C++11 에서 shrink_to_fit() 함수를 사용하면 메모리 영역의 크기도 줄일수 있다.
    • string 의 기본 메모리 크기 이하로는 줄일 수 없다. VC에서는 최소 메모리 할당 크기가 15인데, 사용하더라도 capacity() 는 15 가 된다.
  • 문자열 접근.
//operator [ ] 와 at()을 사용하면 지정된 위치의 문자에 접근할 수 있다. 
s1[2];
s1.at(2);

//front()를 사용하면 첫 번째 문자, back()를 사용ㅇ하면 제일 뒤에 있는 문자에 접근할 수 있다.
s1.front();
s1.back();

//c_str()과 data()를 사용하면 C 언어 형식의 문자열을 얻을 수 있다. 
s1.c_str();
s1.data();
  • 문자열 변경, 비교, 복사
//+ 연산자를 사용하면 string을 서로 연결할 수 있다.
std::string s3 = s1 + s2;
//append()를 이용하면 다른 문자열을 다양한 방식으로 연결 할 수 있다. 
std::string s1 = s2.append(s3);
//insert() 로 특정 위치에 문자열을 삽입힌다.
s1.insert(3, "11 ");
//erase() 를 사용하여 일부 문자열을 삭제한다.
s1.erase(3, 2);
//push_back()을 사용하여 끝에 문자를 추가하거나, pop_back()를 사용하여 끝 문자를 삭제한다.
s1.push_back('s');
s1.pop_back();
//replace()를 사용하여 특정 위치의 문자열을 다른 문자로 바꾼다.
s1.replace(8,7, "abcdefghig");
//swap()를 사용하여 string 끼리 문자열을 바꾼다.
s1.swap(s2);

//문자열을 비교할 때는 compare()를 사용한다. 

//copy() 함수를 사용ㅇ하면 string 문자를 C언어 형식의 문자열 배열에 복사할 수 있다. 
std::string s1("abcdefg");
char dest[16] = {0, };
auto len = s1.copy(dest, 2, 3);
  • 문자열 검색
    • find() 를 사용하여 string 에서 특정 문자열의 위치를 찾는다.
    • rfind() 를 사용하아ㅕ 문자열의 제일 뒤부터 검색을 시작한다.
    • find_first_of() 를 사용하여 가장 처음 검색된 위치를 얻는다.
    • find_last_of() 를 사용하여 가장 마지막에 검색된 위치를 얻는다.
    • find_first_not_of() 를 사용하여 검색할 문자가 아닌 첫 번째 위치를 얻는다.
    • find_last_of() 를 사용하여 검색할 문자가 아닌 마지막 위치를 얻는다.
  • 문자열 일부 복사
    • substr() 함수를 사용하면 string 의 일부를 새로운 string 으로 만들 수 있다.
  • 문자열 변환
    • std::stoi 는 문자열에서 int로 변환한다.
    • std::stol 는 문자열에서 long로 변환한다.
    • std::stoul 는 문자열에서 unsigned long로 변환한다.
    • std::stoll 는 문자열에서 long long 로 변환한다.
    • std::stoull 는 문자열에서 unsigned long long 로 변환한다.
    • std::stof 는 문자열에서 flaot 로 변환한다.
    • std::stod 는 문자열에서 double 로 변환한다.
    • std::stold 는 문자열에서 long double 로 변환한다.
    • std::to_string(), std::to_wstring 는 정수를 string(wstring)로 변환한다.
  • hash
    • hash() 함수를 사용하면 문자열에서 해시 값을 얻을 수 있다.
std::string id1 = "hello";
auto hash1 = std::hash<std::string>()(id1);

난수

  • 아래는 mersenne twister 생성기로 난수를 생성하는 예이다.
//32bit 용
std::mt19937 mtRand;
mtRand();

//64bit 용
std::mt19337_64

//시드값을 이용한 난수 생성
std::mt19937 mtRand(100);

//현재 시간을 시드값으로. 
auto curTime = std::chrono::system_clock::now();
auto dur = curTime.time_since_epoch();
auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(duration).cout();
std::mt19937 mtRand(millis);
  • 위의 mersenne twister 는 의사 난수 생성기중 하나이다. 시드 값에 의해 어떤 난수가 생성될지 예측할 수 있다.
  • random_device 라는 난수 발생시는 비결정적인 난수를 생성한다.
    • 하드웨서 리소스를 이용한다. (하드웨어 노이즈, 마우스 움직임 등)
std::random_device rng;
rng();
  • 지정된 범위 안에서 비슷한 확률로 난수를 생성해야할때, 난수 생성기에 난수 분포기를 더하여 생성하면 된다.
    • 정수형 난수를 분포할때는 uniform_int_distribution
    • 실수형은 uniform_real_distribution
std::mt19937 rng;
//-3 ~ 3 사이의 난수 생성
std::uniform_int_distribution dist(-3, 3);
dist.min();//dist 의 최소 값
dist.max();//dist 의 최대 값

//-3 ~ 3 사이의 난수 생성
dist(rng);

//0.0 ~ 1.0 사이의 난수 생성
std::mt19937 rng2;
std::uniform_real_distribution<double> dist2(0.0, 1.0);
dist2(rang2);
  • n% 확률로 당첨되기 같은 경우 bernouli_distribution 분포기를 사용하면 좋다.
    • 이 확률에 근거하여 true와 false를 반환한다.
std::mt19937_64 rang1(3244);
stdLLbernoulli_distribution dist(0.7);
bool result = dist(rang1);
  • 특정 확률로 n회 실시 했을때 몇 번 성공할 것인가를 확률로 구하고 싶을 때는 binomial_distribution 난수 분포기를 사용하면 좋다.
    • ex. 사망 가능성(확률)이 있는 백신을 N 사람에게 투여할 때 살 수 있는 사람의 수를 구해라.
std::mt19937_64 rang1(3244);
//30% 확률로 1000번 시도했을 때 성공 횟수 구하기.
std::binomial_distribution<> dist(1000, 0.3);
int result = dist(rang1);
  • 평균과 표준편차로 정규 분포한 난수를 생성하고 싶을때에는 normal_distribution 난수 분포기를 사용한다.
    • ex. 평균 키 173cm 표준편차 5cm 의 신장데이터 생성 등을 만들때 유용하다.
std::mt19937_64 rang1(3244);
std::normal_distribution<> dist(173, 5);
int result = dist(rang1);
  • 기본 난수 생성기의 이름은 std::default_random_engine 으로 성능, 크기, 품질등을 고려하여 적당한 것이 선택된다.
std::random_device seed;
std::default_random_engine engine(seed());

알고리즘

  • 데이터 셋(배열, vector, list 등)에 있는 요소들이 특정 조건을 만족하는지 조사할때 아래를 사용 할 수 있다.
    • all_of : 지정 범위에 있는 모든 요소들이 특정 조건을 만족하면 true 반환
    • any_of : 지정 범위에 있는 요소 중 하나라도 특정 조건을 만족하면 true 반환
    • none_of : 지정 범위에 있는 모든 요소들이 특정 조건을 만족하지 않으면 true 반환
  • 데이터셋 요소 중 특정 조건에 맞는 것만 복사하고 싶을 때 copy_if 알고리즘을 사용한다.
  • 데이터셋 요소 중 원하는 개수만 복사하고 싶을대 copy_n 알고리즘을 사용한다.
  • 데이터셋 요소 중 특정 조건에 맞지 안ㅇㅎ는 첫 번째 요소를 찾을 때는 find_if_not 알고리즘을 사용한다.
  • partition_copy 알고리즘을 사용하면 하나의 집단을 서로 다른 두 개의 집단으로 나눌 수 있다.
  • 데이터셋의 정렬 여부를 조사할 때는 is_sorted 알고리즘을 사용한다. 정렬되어있으면 true를 반환한다.
    • 정렬되어있지 않는 요소의 첫번째 위치를 알고 싶을때는 is_sorted_until 알고리즘을 사용한다.
  • 데이터셋이 heap 으로 되어있는지 조사하고 힙이 아닌 요소의 첫번째 위치를 반환하기 위해 is_heap 과 is_heap_util 을 사용한다.
  • 데이터 셋을 연속적인 값으로 채우고 싶을때는 iota 알고리즘을 사용한다.
  • 2개 이상의 값중 최소값과 최고값을 구하기 위해선 std::min, std::max 를 사용한다.
  • std::minmax_element 알고리지ㅡㅁ을 사용하면 데이터셋에서 최소값과 최고값을 찾아준다.
struct CharItme
{
    int itmeId;
    int level;
}
std::vector<CharItem> itmeList;
//...
//all_of 모든 아이템 레벨이 3 이상인가?
std::all_of(itemList.begin(), itemList.end(), [](CharItem item) {return item.level >= 3 ? true : false});

//itemList 에 있는 아이템 중 레벨이 14 이상만 복사하기
std::vector<CharItem> itmeList2;
std::copy_if(itemList.begin(), itemList.end(), std::back_inserter(itmeList2), [](CharItem item){return item.level >= 14; });

//원하는 개수만큼 복사하기. - 아이템리스트에서 3개만 복사하기.
std::vector<CharItem> itmeList3;
std::copy_n(itemList.begin(), itemList.end(), 3 std::back_inserter(itemList3));

//조건에 맞지 않는 요소 찾기.
auto iter = std::find_if_not(itemList.begin(), itemList.end(), [](CHarItem item){return item.level >= 10;});



기타

//아래처럼 클래스나 구조체 멤버에 값을 할당하기 위해 사용할 수 있다. 
class TEST
{
    int n1;
    std::string s1;
}
//...
std::string str1{"hello"};
std::pair<int, int> p = {1,2};
TEST test{1, "test"};

//멤버 변수 정의와 동시에 초기화 할 수 있다.
class TEST
{
private:
    int n = 100;
    std::string s = "test";
}

//생성자에서 같은 클래스의 다른 생성자를 호출 할 수 있다. delegating constructor 를 사용하면 생성자 처리를 하나로 모을 수 있다.
class TEST
{
public:
    TEST() : TEST(0, "test"){}
    TEST(const TEST& test) : TEST(test.n, test.s){}
    //...
private:
    int n;
    std::string s;
}

  • 이전에는 delete 를 동적 메모리 해제에 사용하였지만, C++11에서는 함수를 선언할때 암묵적인 형 변환에 의해서 원하지 않는 함수의 인자형 사용이나 함수 템플릿의 생성 또는 자동으로 생성되는 함수를 막고 싶을때 delete 지정자를 사용한다.
struct TEST
{
    void f(int i)
    {
    }
    void f(double d) = delete;
};
//..
TEST test;
int i = 1;
test.f(i);//ok

double d = 11.0;
test.f(d);//안됨.
  • C++에서는 부모 클래스의 특정 멤버를 오버라이드 할때 virtual 를 앞에 붙이는데,
struct Base
{
    vitrual void foo(int i);
};
struct Derived : Base
{
    virtual void foo(int i);
};

//아래처럼 실수할 수도 있다. 
struct Derived : Base
{
    virtual void foo(float i);
};
//이 문제를 방지하기 위해 override 가 새로 생겼다. 이제 아래처럼 하면 에러를 발생시킨다. 
struct Derived : Base
{
    virtual void foo(float i) override;
};

//Base 클래스의 특정 멤버 함수를 Derived 클래스에서 오버라이드 하지 못하도록 막고 싶을때는 final 를 사용한다.
//더 이상 상속하지 못하게 할때 사용한다. 
struct Base
{
    virtual void foo(int i) final;
};
//아래처럼 하면 컴파일 에러가 발생한다. 
struct Derived : Base
{
    virtual void foo(int i);
};
  • typedef 로 타입의 이름이나 타입 추상화를 할때 사용했는데, using 을 사용하면 템플릿에도 별명을 붙일 수 있다.
template<class T>
using NumVecto = std::vector<T>;
//...
NumVector<int> v = {1,2,3};
  • std::ref 는 변수의 참조를 저장하는 refrecnce_wrapper 객체를 생성한다. 이것은 주로 함수 템플릿에 참조를 전달하고 싶을 때 사용한다. 템플릿에서 인자가 참조임ㅇ르 명시적으로 지정하지 않으면 보통 값으로 전달 된다.
    • const 참조를 사용하고 싶으면 std::cref를 사용한다.
void assign(int& a, inti b){a = b;}
template<typename T> void f(T x, int val){assign(x, val);}
//..
int a = 10;
f(a, 20);//f의 T는 int 형이 된다 a의 값을 변경할수 없다. 
//참조형으로 캐스팅해도 안된다.값 전달이다.
f(int&)(a), 20);
//참조사용을 명시적으로 한다. 된다.
f<int&>(a, 20);
//std::ref 를 사용하면 쉽다.
f(std::ref(a), 20);
  • std::function 은 다양한 함수 포인터, 함수 객체, 멤버 함수 포인터, 멤버 변수 포인터를 저잗ㅇ할 수 있는 템플릿 클래스다.
    • function 의 템플릿인수는 반환값 타입(인수 값 타입 리스트)라는 형식으로 함수의 시그니처를 지정한다
int add(int x, int y){return x+y;}
std::function<int(int, int)> func = add;
auto result = func(1, 2);

class TEST
{
    void print_add(int i) const
    {
        //...
    }
    int n_num;
};
int add_num(int x, int y)
{
    //...
}

//전역 함수 저장
std::function<int(int, int)> func_add_num = add_num;
auto result1 = func_add_num(1, 2);

//람다 함수 저장
std::function<int()> func_lamdba = [](){return 42;};
auto result2 = func_lamdba();

//클래스(구조체)의 멤버 함수 저장
std::function<vpod(ocnst TEST&, int)> f_add_display = &TEST::print_add;
TEST test = {11};
f_add_display(test, 1);
  • mem_fn 은 지정한 멤버 함수를 호출할 수 있는 타입 객체를 생성하여 반환한다.
class TEST
{
    void a()
    {
    }
    void b(int i)
    {
    }
    int data = 5;
}


TEST test;
//파라미터가 없는 멤버 함수 지정
auto a1 = std::mem_fn(&TEST::a);
a1(test);

//파라미터가 있는 멤버 함수 지정
auto b1 = std::mem_fn(&TEST::b)
b1(test, 42);

//멤버 변수 저장
auto c1 = std::mem_fn(&TEST::data);
c1(test);

  • std::next 는 반복자를 n 개 만큼 앞으로 이동하고, std::prev 는 반복자를 n개만큼 뒤로 돌아간다.
  • std::begin 와 std::end 는 컨테이너 클래스들이 멤버로 가지고있는 반복자 begin 과 end 와 같다.
std::vector<int> v{1,2,3,4,5};
auto it = v.begin();
auto nx = std::next(it, 2);
auto pv = std::prev(nx, 2);
it = std::begin(v);
it = std::end(v);