2016년 7월 7일 목요일

C++ STL 수업 - 3일차


스마트 포인터

#include "show.h"
#include <memory>
/*
스마트 포인터
C/C++에서는 new는 한곳이지만, delete는 여러 곳에 존재.
아이디어 : 포인터는 블럭 벗어나도 사라지지 않지만,
           [객체]는 블럭 벗어날 때 자동 delete된다.

실제는 [객체]이지만, *, ++, --, -> 등 override해서 지원. 마치 포인터 처럼 사용.
3가지 : shared_ptr, weak_ptr, unique_ptr
*/

class Car
{
public:
    void Go() { cout << "Car Go" << endl; }
    ~Car()    { cout << "Car 파괴" << endl; }
};

#if 0
int main()
{
    Car *p = new Car;
    delete p;
}
#endif


int main()
{
    //    shared_ptr<Car> p = new Car;  // ERROR. explicit 생성자는 =로 할당 불가능
    shared_ptr<Car> p(new Car);   // OK. 생성자가 explicit 생성자 이기 때문이다.
                                  // shared_ptr<Car>는 객체다. 포인터는 아니다.
    p->Go(); // 그럼에도 p에 ->를 붙여 포인터처럼 쓰는 것은 -> operator를 재정의 한것이다.
             // 스콥이 나가면 Car는 destroy 된다. 때문에 delete 불필요. 게임 업체에서는 진짜 포인터 안씀.
    return 0;
}



#include "show.h"
#include <memory>
/*
스마트 포인터
C/C++에서는 new는 한곳이지만, delete는 여러 곳에 존재.
아이디어 : 포인터는 블럭 벗어나도 사라지지 않지만,
[객체]는 블럭 벗어날 때 자동 delete된다.

*/

class Car
{
public:
    void Go() { cout << "Car Go" << endl; }
    ~Car() { cout << "Car 파괴" << endl; }
};

/* p1 p2도 둘다 같은 object를 가리키게 됨.
참조 개수를 관리 한다. 모든 스마트 포인터가 같은 참조 개수를 공유 한다.

1
2
1
A
Car 파괴
*/
int main()
{
    shared_ptr<Car> p1(new Car);

    cout << p1.use_count() << endl;
    {
        shared_ptr<Car> p2 = p1;
        cout << p1.use_count() << endl;
    }
    cout << p1.use_count() << endl;

    cout << "A" << endl;
    return 0;

} // ~Car() 불림.




#include "show.h"
#include <memory>
/*
스마트 포인터
C/C++에서는 new는 한곳이지만, delete는 여러 곳에 존재.
아이디어 : 포인터는 블럭 벗어나도 사라지지 않지만,
[객체]는 블럭 벗어날 때 자동 delete된다.

*/

class Car
{
    Car(int c){}
    int color;
public:
    void Go() { cout << "Car Go" << endl; }
    ~Car() { cout << "Car 파괴" << endl; }
};

void* operator new(size_t sz)
{
    cout << "new : " << sz << endl;
    return malloc(sz);
}

void operator delete(void*p)
{
    free(p);
}

/*

int main()
{
    // 아래 코드가 실행되면 메모리 할당이 몇번 일어 날까요 ?
    shared_ptr<Car> p1(new Car);
   
    1. HEAP  : new Car에 의해서 한번 발생 (new 1회)
    2. STACK : p1은 stack에 생김.
    3. HEAP  : [참조 개수] + 알파를 관리하기 위한 관리 객체가 따로 만들어짐. (new 1회)
    > 1과 3 각각 1회씩 2회의 new가 발생함.
    > 3때매 파편화 문제 발생.
    > 대응 방법으로, 1에 붙여서 3을 할당 받는 방법 생각 가능.
   

}
*/

int main()
{
    shared_ptr<Car> p1(new Car);
    // new : 4  원본객체
    // new : 16 참조개수관리객체
   
    // sizeof(Car) + sizeof(참조개수관리객체) 크기를 한번에 메모리 할당.
    // shared_ptr 쓴다면 반드시 make_shared 써야 한다 !
    shared_ptr<Car> p2 = make_shared<Car>(10);
    // 이번에는 new가 전체 1회만 발생 함.
    // new : 16 원본객체+참조개수관리객체
   
    /*
    make_shared<>로 (1) 원본객체(4바이트)와 (2) 참조개수관리객체(16바이트)를 합쳤음에도 20바이트가 아닌 16바이트인 것은
    원래 참조개수관리객체가 별도로 존재할 때 크기 16바이트 중 4바이트는 원본객체에 대한 포인터임.
    그런데 참조개수관리객체가 원본객체 끝에 달라붙으면서 그 4바이트가 필요 없게 됨.
    그래서 참조개수관리의 크기는 12바이트가 되고, 원본객체의 크기(int color 4바이트)를 더해서 16바이트가 됨.
    */
}




#include "show.h"
#include <memory>
#include <type_traits> //remove_pointer

#include <windows.h>
void foo(int *p)
{
    cout << "foo" << endl;
    delete[] p;
}

struct ArrayDeleter
{
    void operator()(int *p) { delete[] p; }
};

int main()
{
    //shared_ptr<포인터 뺀타입> p(포인터 타입);
    // 삭제자의 전달.
    shared_ptr<int> p1(new int); // new는 delete로 삭제
    shared_ptr<int> p2(new int[10], foo); // new[]는 delete[]로 삭제해야 함. 때문에 삭제자를 전달해서 삭제 가능.
    shared_ptr<int> p3(new int[10], ArrayDeleter()); // 임시객체로 전달
    shared_ptr<int> p4(new int[10], [](int *p) {delete[] p;}); // 람다로 전달 C++11

    shared_ptr<FILE> p5(fopen("a.txt", "wt"), fclose); // 삭제자를 fclose로 제공. 인자가 1개 이상이면, bind를 사용할 수 있다.

    //shared_ptr<포인터 뺀타입> p(포인터 타입); 이어야 하는데,
    // 아래와 같이 HANDLE도 포인터 타입일 때 처리 방법.
    HANDLE h = CreateEvent(0, 0, 0, 0);
    CloseHandle(h);
    shared_ptr<remove_pointer<HANDLE>::type> h2(CreateEvent(0, 0, 0, 0), CloseHandle);

    // 현재는 삭제자를 쓸 때는 make_shared<>를 못 씀 !
    // make_shared<> 사용시 삭제자 변경 불가능. 이후 표준에서 논의 중. 표준 위원회에 요구 사항 많음.
#if 0
    int *p1 = new int;
    delete p1;

    int *p2 = new int[10];
    delete p2;
    // C++ 표준에는 new -> delete, new [] -> delete[]만 써 있음. 그외에는 정의 안되어 있음. 그것은 컴파일러 따라 다름. "undefined" 항목.
    // 때문에 new [] -> delete는 쓰면 안됨.
#endif   

}



#include "show.h"
#include <memory>

// 중요 예제...
// 참조계수 스마트 포인터가 상호 참조가 발생하면 메모리 누수 발생 함.
struct Node {
    int data;
    // Node* next;
    // 스마트 포인터를 쓰기로 했으면, 일반 포인터와 스마트를 섞는것보다, 모두 스마트로 하는게 좋다.
    shared_ptr<Node> next;

    ~Node() { cout << "Node파괴" << endl; }
};

/*
  스마트 포인터를 쓰기로 했으면, 일반 포인터와 스마트를 섞는것보다, 모두 스마트로 하는게 좋다.

*/
#if 0
int main()
{
    shared_ptr<Node> p1(new Node);
    shared_ptr<Node> p2(new Node);

    // 스마트 포인터를 써도, 아래 경우 Node가 파괴가 안됨.
    // [상호 참조]가 일어나면 메모리 해지가 안되어 [메모리 누수] 발생 함.
    p1->next = p2;
    p2->next = p1;
}
#endif


struct Node2 {
    int data;
    // Node* next;
    // 스마트 포인터를 쓰기로 했으면, 일반 포인터와 스마트를 섞는것보다, 모두 스마트로 하는게 좋다.
    weak_ptr<Node2> next;

    ~Node2() { cout << "Node2파괴" << endl; }
};

// 해결책으로, 참조개수가 증가되지 않는 스마트 포인터가 필요. weak_ptr<>
// s로 시작 하는 스마트 포인터는 모두 참조 계수 개체
// w로 시작 하는 스마트 포인터는 weak 참조
int main()
{
    shared_ptr<Node2> p1(new Node2);
    shared_ptr<Node2> p2(new Node2);

    p1->next = p2;
    p2->next = p1;
}


일반 포인터가 아닌 weak_ptr<>을 써야 하는 이유 
#include "show.h"
#include <memory>

int main()
{
    int *p = 0;

    weak_ptr<int> wp;
    {
        shared_ptr<int> sp(new int);

        cout << sp.use_count() << endl; // 1
       
        p = sp.get();
        wp = sp;

        cout << sp.use_count() << endl; // 1
    } // shared_ptr<int> sp(new int); 자원 파괴 됨.

    cout << p << endl;
    // 이 경우 [자원]은 파괴 되었지만, 일반포인터 p가 [주소]는 계속 가지고 있음.
    // p는 [자원]이 진짜 살아 있는지 아닌지 알 수 있는 방법이 없음.

    // 해결책:
    // wp는 자원뿐만 아니라, 참조개수관리객체도 가리키게 됨.
    // wp가 하나라도 살아 있으면 [자원 객체]는 파괴되더라도 [참조개수관리객체]는 살아 있게 됨.
    // weak_ptr<>를 사용시 자원이 파괴 되었는지 알 수 있다.

    cout << wp.use_count() << endl; // 0 : [자원]이 파괴 되었음을 알 수 있음.
    // make_shared여도 역시 가능.

    // 진짜 중요한 것은 아래와 같이 쓸 수 있느냐? 절대 안됨. 멀티 쓰레드 환경에서는 문제가 됨.
#if 0
    if (wp.use_count() > 0) { // 여기가 true이다가 다른 쓰레드에 의해서 파괴되는 경우.
        // 자원이 살아 있다고 판단하고 사용 하는 경우
        wp->멤버 호출(); // 이 시점에는 false 일 수 있다.
    }
#endif
    // 때문에 wp는 ->연산자를 지원하지 않는다.
    // 즉, 자원 접근을 허용하지 않는다 !!!

    // weak_ptr<>로 shared_ptr<>을 다시 [생성]해야 합니다 !!
    shared_ptr<int> sp2 = wp.lock();
    // 여기 실행되기 전에 죽으면 상관 없으나, [자원]이 있었으면 다른 thread에 의해서 파괴될 수 없다.
    // 기 자원이 파괴된 경우는 sp2는 0;
    // lock이 참조개수의 thread safe을 보장 한다. wp.lock()은 shared_ptr<>을 하나 생성해서 넘겨 준다.
   
    // Android는 wp.promote();
    if (sp2 == 0)
        cout << "자원 파괴 됨" << endl;
    else
        cout << "자원 사용 가능" << endl;

}


쓰레드와 스마트 포인터가 섞여 있을 때 문제 점
#include "show.h"
#include <memory>
#include <windows.h>

// cafe.naver.com/cppmaster 예전 수업 자료실에서 "키캣" 검색해서 받아 보기
// core/core/libutils/thread.cpp

// 3번째 파라미터를 thread로 실행 함.
class Thread
{
public:
    void run() { CreateThread(0, 0, foo, this, 0, 0); }
    static DWORD __stdcall foo(void*p) {
        Thread* self = (Thread*)p;
        self->threadMain();
        return 0;
    }

    virtual void threadMain(){}
};

class MyThread : public Thread
{
    int data;
public:
    virtual void threadMain() { data = 10; cout << "my thread" << endl; }
};

int main()
{
    {
        shared_ptr<MyThread> p(new MyThread);
        p->run();
    }
    // 이 블럭을 벗어나는 순가 p는 destory 됨. 반면 thread는 동작 중.
    // 쓰레드 객체의 수명을 쓰레드가 죽을 때까지 이어야 한다.
    // 그 방법?

    while (1);
}


#include "show.h"
#include <memory>
#include <windows.h>

// 쓰레드와 스마트 포인터가 섞여 있을 때 문제 점

// cafe.naver.com/cppmaster 예전 수업 자료실에서 "키캣" 검색해서 받아 보기
// core/core/libutils/thread.cpp

// 그럼 언제 shared_ptr<>을 증가시켜줄 것인가가 문제.
class Thread
{
    shared_ptr<Thread> mHoldSelf;// 자신의 참조 계수를 증가하기 위해.

public:
//    Thread() : mHoldSelf(this) {} // 방1. 생성자에서 해도 되는가 ? 안됨. thread 안만들면 메모리 leak이 발생. 때문에 thread 만들 때 해야 함.

    void run(shared_ptr<Thread> sp) {
        //mHoldSelf = this; // 방2. 이렇게 해도 될까 ?
        mHoldSelf = sp; // 방3.
        cout << ">"<<mHoldSelf.use_count() << endl;
        CreateThread(0, 0, foo, this, 0, 0);
    }
    static DWORD __stdcall foo(void*p) {
        Thread* self = (Thread*)p;
        self->threadMain();

        // 참조 계수를 줄여 준다. mHoldSelf은 더이상 [자원] 참조하지 않는다. 참조계수를 무조건 0으로 만드는 것은 아님
        self->mHoldSelf.reset();
        return 0;
    }

    virtual void threadMain() {}
};

class MyThread : public Thread
{
    int data;
public:
    virtual void threadMain() { data = 10; cout << "my thread" << endl; }
    ~MyThread() { cout << "~MyThread" << endl; }
};

int main()
{
    {
        shared_ptr<MyThread> p(new MyThread);
        p->run(p);
    }
    // 이 블럭을 벗어나는 순가 p는 destory 됨. 반면 thread는 동작 중.
    // 쓰레드 객체의 수명을 쓰레드가 죽을 때까지 이어야 한다.
    // 그 방법?

    while (1);
}

/*
방2 안되는 이유.
shared_ptr<int> p1(new int);
shared_ptr<int> p2 = p1; // 참조 계수 2

int *p = new int;
shared_ptr<int> p3(p); // 계수 1
shared_ptr<int> p4(p); // 계수 1
*/



#include "show.h"
#include <memory>
#include <windows.h>
// 쓰레드와 스마트 포인터가 섞여 있을 때 문제 점

// 외부에서 shared_ptr<>로 [객체]를 관리할 때
// [객체] 스스로가 자신의 참조개수를 증가하고 싶다면
// enable_shared_from_this<> 사용 한다.

class Thread : public enable_shared_from_this<Thread> //this 로 부터 shared_ptr의 참조 개수를 증가하게 해달라
{
    shared_ptr<Thread> mHoldSelf;// 자신의 참조 계수를 증가하기 위해.

public:

    void run() {
        mHoldSelf = shared_from_this(); // 외부에서 만든 shared_ptr<>이 사용하는 관리 객체를 공유하게 한다.
        cout << ">" << mHoldSelf.use_count() << endl;
        CreateThread(0, 0, foo, this, 0, 0);
    }

    static DWORD __stdcall foo(void*p) {
        Thread* self = (Thread*)p;

        // 방2. mHoldSelf를 [지역변수]에 옮겨 담는다. reset 이후에 실수로 코드 불러서 문제 생기는 것 방지 할 수 있음 !
        shared_ptr<Thread> me(self->mHoldSelf);
        self->mHoldSelf.reset();

        self->threadMain();
        // 방1. self->mHoldSelf.reset(); 이것 대신
          // 방1 사용시 cout << data << end; // 문제 발생
        return 0;
    }

    virtual void threadMain() {}
};

class MyThread : public Thread
{
    int data;
public:
    virtual void threadMain() { data = 10; cout << "my thread" << endl; }
    ~MyThread() { cout << "~MyThread" << endl; }
};

int main()
{
    {
        shared_ptr<MyThread> p(new MyThread);
        p->run();
    }
    while (1);
}



#include "show.h"
#include <memory>

/*
관리 객체
*/
int main()
{
    shared_ptr<int> p1(new int); // 자원 + 관리객체 생성
    shared_ptr<int> p2 = p1;


    unique_ptr<int> up1(new int); // 자원만 생성. 관리 객체가 없다. 소멸자에서 delete 기능만 제공.
    //unique_ptr<int> up2 = up1; // unique_ptr<>은 복사 생성자를 지워버림. compile error 발생.

    unique_ptr<int> up2 = move(up1); // move은 원본 reset. unique_ptr은 복사는 안되지만 move는 가능 함.

    *up2 = 10;

    cout << *up2 << " sz : "  << sizeof(up2) << endl;

    *up1 = 10; // runtime error - up1은 자원이 up2에 전달 됨. unique_ptr은 int* 사용 대비 성능 저하가 없음.
    // 컴파일 시 모두 inline으로 교체 됨.

}




#include "show.h"
#include <memory>

struct Freer
{
    inline void operator()(void*p)
    {
        cout << "Freer" << endl;
        free(p);
    }
};

// 삭제자 전달 방법
int main()
{
    // 삭제자 전달  : 템플릿 인자로 전다
    // 장점 : 삭제자를 별도로 보관할 필요가 없고, 인라인 치환된다.
    // 단점 : 삭제자가 다르면 다른 타입이 된다. 같은 컨테이너에 보관이 안된다.
    unique_ptr<int> p1(new int);
    unique_ptr<int, Freer> p2((int*)malloc(100)); // unique_ptr은 관리 객체가 없음. 삭제자 지정 방법 필요 ==> template인자로 전달.
    // 탬플릿 인자가 다르면 다른 타입이다.
   
    // shared_ptr<int> p3((int*)malloc(100), foo); // 관리 객체에는 삭제자 정보도 보관.
    // shared_ptr은 왜 다르게 쓰나 ? 삭제자가 다르면 다른 타입이 되기 때문에 함께 vector같은 자료 구조로 묶을 수 없다.
    // shared_ptr<int> p3((int*)malloc(100), foo);

    unique_ptr<int[]> p3(new int[10]); // new T[] 형태면 템플릿 파라미터에 T[]를 넘겨 준다.
}



#include "show.h"
#include <memory>

int main()
{
    shared_ptr<int> sp1(new int);
    unique_ptr<int> up1(new int);

    // 다음 중 에러를 모두 골라 보세요
    shared_ptr<int> sp2 = up1; // ERROR unique_ptr 자원을 공유 하게 되기 때문에 안됨.
    unique_ptr<int> up2 = sp1; // ERROR 나 말고도 다른 놈도 있기 때문에 안됨.

    shared_ptr<int> sp3 = move(up1); // OK : move로 독점권을 포기하는 것이기 때문에 안됨.
    unique_ptr<int> up2 = move(sp1); // ERROR 나 뿐만 아니라, 다른 놈도 가르키고 있을 수 있기 때문에 안됨.
}


쓰레드

#include "show.h"
#include <chrono>
using namespace chrono;


// c++11부터 STL에서 스레드 지원 함.
#include <thread>

void foo(int a, double d)
{
    cout << "foo" << endl;
    this_thread::sleep_for(1min);
}

class FOO {
public:
    void operator()() {
        cout << "FOO" << endl;
    }
    void goo() {
        cout << "goo" << endl;
    }
};

int main()
{
    //thread t(bind(&foo, 1, 3.4)); // 스레드 바로 실행 됨.
    //thread t(&foo, 1, 3.4); // 스레드 바로 실행 됨. c++11 가변인자 템플릿으로 인해 가능 함.
   
   
    // thread t(FOO()); // ERROR 임시 객체 못 받음.
   
    FOO f;
    //thread t(f); // ERROR 임시 객체 못 받음.

    thread t(bind(&FOO::goo, &f)); // bind가 인자 고정뿐만 아니라, 객체 고정도 가능하다.
    t.join(); // 자식 스레드가 죽을 때까지 기다려 줌. 안기다리면 crash.
}


#include "show.h"
#include <chrono>
using namespace chrono;

#include <thread>
#include <mutex>
mutex m;
long x = 10;

void foo()
{
    m.lock();
    x = 100; // 여기서 exception이 난다면 ?  C++에서는 mutex중 exception나면 deadlock
    m.unlock();
}

void foo2()
{
    lock_guard<mutex> lg(m); // lock_guard 생성자에서 m.lock하고 소멸자에서 unlock.
    x = 100;
}
// C++에서는 코드 플로우 중 exception 발생하면, 로컬 변수는 destroy 한다.
// 즉, try{ } 안에 선언된 lock_guard는 catch에 도달할 때 destroy 됨이 보장 된다.
// Android Framework에서는 lock_guard에 상당하는 Autolock 클래스 지원.

int main()
{
    thread t1(&foo2);
    thread t2(&foo2);

    t1.join();
    t2.join();
}


begin
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

#if 0
// 일반 함수 begin
template<typename T> auto begin(T&c) { return c.begin(); }
template<typename T> auto end(T&c) { return c.end(); }

template<typename T, int N> auto begin(T (&c)[N]) { return c; } // T (&c)[N] 배열 의미
template<typename T, int N> auto end(T (&c)[N]) { return c+N; }
#endif

// c.begin() 대신 begin(c)를 쓰라. 그러면 포인터도 처리 가능. C++11부터 지원
template<typename T> void show(T& c)
{
    auto p = begin(c);
    while (p != end(c)) {
        cout << *p << " ";
        ++p;
    }
    cout << endl;
}

int main()
{
    vector<int> v = { 1,2,3,4,5 };
    show(v);

    int x[5] = { 1,2,3,4,5 };
    show(x); // ??
}

//기타 : c++11은 call by value로 가자라고 함...


해시
#include "show.h"
#include <set> // RB [tree]
#include <unordered_set> // [hash] Table

int main()
{
    hash<int> hi; // 해쉬 함수(함수 객체로 되어 있음)
    cout << hi(10) << endl;
    cout << hi(20) << endl;
    cout << hi(10) << endl;

    hash<string> hs;
    cout << hs("hello") << endl;
    cout << hs("whatup") << endl;
    cout << hs("hello") << endl;

    cout << hash<double>()(3.4) << endl;
}

#include "show.h"
#include <set> // RB [tree]
#include <unordered_set> // [hash] Table

int main()
{
    unordered_set<int> s;

    s.insert(10);
    s.insert(30);
    s.insert(15);
    s.insert(20);

    pair<unordered_set<int>::iterator, bool> ret = s.insert(20); // 중복 허용하지 않음. Fail
    if (ret.second == false)
        cout << "실패" << endl;

    // s에서 30을 찾아서 출력
    unordered_set<int>::iterator p = s.find(30);
    cout << *p << endl;


}


#include "show.h"
#include <set> // RB [tree]
#include <unordered_set> // [hash] Table

struct People
{
    string name;
    int age;

    People(string name = "", int a = 0) : name(n), age(a) {}

};

int main()
{
    set<People> s1;
    s1.insert(People("kim", 10)); // set은 크기 비교 tree다. 거기에 People을 추가하려면, 비교 방법이 필요 함.
                                   // 방법1. People에 < 연산자가 필요하다.
                                   // 방법2. set<People, PeopleComparee> 등으로 비교함수 객체가 있으면 된다.
                                   // 순위는 방법2번이 우선 순위가 높다. 2가 있으면 2로 대체 된다.

    //unordered_set<People> s2;
}


#include "show.h"
#include <set> // RB [tree]
#include <unordered_set> // [hash] Table

struct People
{
    string name;
    int age;
    People(string n = "", int a = 0) : name(n), age(a) {}
};

// 사용자정의 타입을 넣으려면 2가지 필요 함.
// 1. 해쉬 함수 (함수객체)로 만들어야한다.
// 2. 상등 비교 함수객체가 있어야 한다.

struct PeopleHash
{
    inline size_t operator()(const People&p) const
    {
        return hash<string>()(p.name) + hash<int>()(p.age);
    }
};

struct PeopleEqual {
    inline bool operator() (const People&p1, const People& p2) const
    {
        return p1.name == p2.name && p1.age == p2.age;
    }
};

int main()
{
    unordered_set<People, PeopleHash, PeopleEqual> s1;
    s1.insert(People("kim", 10)); // 되게 하려면 ??
    s1.insert(People("m", 30));
    s1.insert(People("park", 40));

}



#include "show.h"
#include <set> // RB [tree]
#include <unordered_set> // [hash] Table

struct People
{
    string name;
    int age;
   
    People(string n = "", int a = 0) : name(n), age(a) {}
};

#if 0
template<typename T> class hash {}; //primary template
template<> class hash<int> { // 전문화
    inline size_t operator()(int a) { }
};
template<> class hash<string> { // 전문화
    inline size_t operator()(string a) { }
};
#endif

// C++ 표준의 hash primary template을 People버전으로 [전문화](specialization)
template<> class hash<People> { // 전문화
public:
    inline size_t operator()(const People&p) const
    {
        return hash<string>()(p.name) + hash<int>()(p.age);
    }
};

template<> class equal_to<People>
{
public:
    inline bool operator() (const People&p1, const People& p2) const
    {
        return p1.name == p2.name && p1.age == p2.age;
    }
};

// 사용자정의 타입을 넣으려면 2가지 필요 함.
// 1. 해쉬 함수 (함수객체)로 만들어야한다.
// 2. 상등 비교 함수객체가 있어야 한다.

struct PeopleHash
{
    inline size_t operator()(const People&p) const
    {
        return hash<string>()(p.name) + hash<int>()(p.age);
    }
};

struct PeopleEqual {
    inline bool operator() (const People&p1, const People& p2) const
    {
        return p1.name == p2.name && p1.age == p2.age;
    }
};

int main()
{
    unordered_set<People, PeopleHash, PeopleEqual> s1;
    s1.insert(People("kim", 10)); // 되게 하려면 ??
    s1.insert(People("m", 30));
    s1.insert(People("park", 40));

    // 만약에
    unordered_set<People> s2;
}

/*
중요 !!! 기본 개념.
template<typename T> class stack{}; // 임의의 타입에 대해 클래스 찍어 냄. (1) primary template
template<typename T> class stack<T*>{}; // 모든 타입은 1번. 포인터일때는 이것 사용. (2) 부분 전문화(특수화, 특화). partial specialization
                                        // 더블 포인터도 여기 탄다.
template<> class stack<int>{}; // int일때는 이것 사용. (3) 전문화(특수화, 특화) 버전 specialization

stack<double> s1;
stack<int*> s2;
stack<int> s2;

*/







알고리즘

#include "show.h"
// 100여개 알고리즘.

// 핵심 1: remove() 등의 알고리즘은 컨테이너 자체의 크기를 줄이지는 않음. 삭제가 아님. 리턴 값만 알려줌 !!
int main()
{
    vector<int> v = { 1, 2,3,4, 5, 3, 7, 8, 3, 10 };
    vector<int>::iterator p = remove(v.begin(), v.end(), 3); // 주어진 범위에서 3을 찾아서 삭제. list, vector, deque, 배열도 됨.
    // 컨테이너를 줄이지는 않음. 나머지 요소를 앞으로 당겨 옴.
    // p는 유효한 값 [다음] 위치를 돌려 줌.

    show(v);
    cout << *p << endl;

    v.erase(p, v.end()); // 여기서 실제 지우는 동작 함 !
    show(v);
}

#include "show.h"
// 총 4가지 변형이 존재 한다 : remove, remove_if, remove_copy, remove_copy_if

using namespace std::placeholders;
int main()
{
    vector<int> v = { 1, 2,3,4, 5, 3, 7, 8, 3, 10 };
    vector<int> v2(10);

    // 알고리즘의 4가지 변경.
    // vector<int>::iterator p = remove(v.begin(), v.end(), 3); // (1) 상수 버전
    // vector<int>::iterator p = remove_if(v.begin(), v.end(), foo); // (2) 조건자 버전 (함수)
    // vector<int>::iterator p = remove_if(v.begin(), v.end(), bind(modulus<int>(), _1, 2)); // (2) 조건자 버전(함수 객체)
    // vector<int>::iterator p = remove_if(v.begin(), v.end(), [](int a) { return a % 2 == 0;}); // (2) 조건자 버전(람다)
    //v.erase(p, v.end());

    // (3) 알고리즘의 복사 버전.
    vector<int>::iterator p = remove_copy(v.begin(), v.end(), v2.begin(), 3); // loop가 1회만 돈다. copy + remove 해도 되나 그보다 빠르다.
    // 여기서 p는 v2의 반복자
    // 반면, sort + copy는 성능차이가 많이 나기 때문에 sort_copy가 없다.
    // show(v);

    //show(v2);
    //v2.erase(p, v2.end());
    //show(v2);

    // (4) 조건자 복사 버전
    vector<int>::iterator p = remove_copy_if(v.begin(), v.end(), v2.begin(), [](int a) { return a % 2 == 0;});
}


#include "show.h"

int main() {
    int x[5] = { 1, 2, 3,4, 5 };
    int *p1 = find(x, x + 5, 3); // 상수 버전
    int *p2 = find_if(x, x + 5, [](int a) {return a % 2 == 0;}); // 조건자 버전
    cout << *p2 << endl;

    sort(x, x + 5); // < 연산으로 비교
    sort(x, x + 5, greater<int>()); // >로 비교
    // sort냐 sort_if냐 ? 왜 sort냐? find는 find_if는 같은 인자개수.
    // 조건자 버전이라도 parameter overloading 가능 하면 그대로 쓴다.
}


#include "show.h"
#include <numeric>

// (1) (2) (3) : algorithm 헤더
// (4) : numeric 헤더
int main() {
    int x[5] = { 1,2,3,4,5 };

   find(x, x + 5, 3); // 내부 구조 안바뀜. (1) 변경 불가 sequence 알고리즘.
   reverse(x, x + 5); // 내부 구조 바뀜.   (2) 변경 가능 sequence 알고리즘.
   sort(x, x + 5); //                     (3) 정렬 알고리즘.

   int n = accumulate(x, x + 5, 0); //    (4) 범용 수치 알고리즘.
   cout << n << endl;
}


#include "show.h"
#include <numeric>

// 범용 수치 알고리즘은 연산자를 바꿀 수 있다.
int main() {
    int x[5] = { 1,-2,3,4,5 };
    int y[5] = { 0 };
    int z[5] = { 0 };

    int n = accumulate(x, x + 5, 0); // 기본 버전 : + 사용
    int n2 = accumulate(x, x + 5, 1, multiplies<int>()); // * 사용 1~5까지의 곱 = 5!
    int n3 = accumulate(x, x + 5, 0, [](int a, int b) {return abs(a) + abs(b);}); // abs
    cout << n3 << endl;

    //partial_sum(x, x + 5, y);
    partial_sum(x, x + 5, y, multiplies<int>());
    for (int i = 0; i < 5; i++)
        cout << y[i] << " ";
}

// www.boost.org : 표준과 별도의 라이브러리 만들어보자.
// 아무도 이해할 수 없다 ㅠ;



#include "show.h"
#include <numeric>

int main() {
    vector<int> v = { 1, 2, 3,4, 5 };
    vector<int>::iterator p = copy(v.begin() + 1, v.end(), v.begin());

    show(v);
    v.erase(p, v.end());
    show(v);

    vector<int> v2 = { 1, 2, 3, 4, 5 };
    vector<int>::iterator p2 = copy(v2.begin(), v2.end()-1, v2.begin()+1);
    show(v2);

    // 예전엔 전부 1일 나왔음. 현재는 뒤에서부터 옴김.
    // 때문에 예전엔
    vector<int> v3 = { 1, 2, 3, 4, 5 };
    copy_backward(v3.begin(), v3.end() - 1, v3.end());
    show(v3);

    string s = "ABCD";
    do {
        cout << s << endl;
    } while (next_permutation(s.begin(), s.end()));
}


WebKit이나 Chromium에 쓰이는 RefCounted는 아예 RefCount<>를 상속 받아서 쓴다.
http://www.suninf.net/2013/11/refcounted-in-chrome.html
template <class T>
class RefCounted;

class MyFoo : public base::RefCounted<MyFoo>
{
protected:
  ~MyFoo() {}
  friend class base::RefCounted<MyFoo>;
};

C++ STL 수업 - 2일차

컨테이너의 저장 타입

컨테이너는 아래 typedef를 갖는다.
tyepdef T value_type;

#include "show.h"
void print_first_element(vector<int>&v)
{
    int n = v.front();
    cout << n << endl;
}

template<typename T>
void print_first_element(vector<T>&v)
{
    typename T n = v.front();
    cout << n << endl;
}

template<typename T>
void print_first_element(T &v)
{
    // T : list<double>
    T::value_type n = v.front();
    cout << n << endl;
}

int main()
{
    vector<double> v = { 1, 2, 3 };
    /* int형이 아니라 double로 바꾼다면 ?
       vector가 아니라 list라면 ?
    */
   
    print_first_element(v);

}

/*
template 기반의 container들은 자신이 [저장하는 타입]이 있음.
외부에서 [그 저장하는 타입]을 알고 싶을 때가 있음.
그래서 아래처럼 만들 수 있음.

template<typename T> class list
{
public:
tyepdef T value_type;
// ...
};

list<int> s = { 1, 2, 3 };
list<int>::value_type n = s.front(); // 이런 식으로는 쓰지 않겠지만,

template<typename T>
void printf_first_element(T&v)
{
   ? n = v.front();
   //여기서 컨테이너 T가 저장하는 데이터의 타입을 알 수 있는 방법이 필요하다. 그래서 value_type을 사용한다.
   cout << n << endl;
}

다만, T::value_type은 보통은 static 멤버로 생각함.
그래서 명확하게 typename으로 보게 하도록 하기 위해서
typename T::value_type n = v.front()처럼, typename이라는 키워드를 넣어 준다.
즉, 컨테이너 안에 있는 typedef된 타입을 사용할 때는 꼭 typename을 앞에 붙여 준다.

또한 컨테이너의 크기를 얻어 올때도
int sz = v.size(); 보다는 아래와 같이
list<double>::size_type sz = v.size();로 하는게 정확하다.
typename을 붙이지 않는다. 템플릿일 때만 typename을 붙여 준다.
즉,

typename list<double>::size_type sz = v.size(); // ERROR
typename T::size_type sz = v.size(); // OK

*/



반복자의 value_type
#include "show.h"

// 주어진 구간의 합을 구하는 함수
int sum(int* first, int *last)
{
    int s = 0;
    while (first != last) {
        s = s + *first;
        ++first;
    }
    return s;
}

/*반복자도 자신이 가르키는 타입이 있습니다.*/
template<typename T> typename T::value_type
sum2(T first, T last)
{
    typename T::value_type s = 0;
    while (first != last) {
        s = s + *first;
        ++first;
    }
    return s;
}

/*
반복자의 종류
1. 진짜 포인터
2. 객체형 포인터
위 2개의 차이점 때문에 일반화 함수를 만들기 어렵습니다.

해결책 : 포인터 반복자와 객체형 반복자를 구분 정의.
template<typename T> struct iterator_traits
{
    typedef typename T::value_type value_type;
};

// 부분 정의 : 아래 <T*>는 T가 진짜 pointer일때는 컴파일 시 이 정의를 사용하라는 의미.
template<typename T> struct iterator_traits<T*>
{
    typedef T value_type;
};
*/

template<typename T> typename iterator_traits<T>::value_type
sum3(T first, T last)
{
    typename iterator_traits<T>::value_type s = 0;
    while (first != last) {
        s = s + *first;
        ++first;
    }
    return s;
}



int main()
{
    int x[10] = { 1,2,3,4,5,6,7,8,9,10 };
    cout << sum(x, x + 10) << endl;
    // int는 어떻게 하나 ? int타입은 value_type을 지원하는가 ?

    vector<int> v = { 1,2,3,4,5,6,7,8,9,10 };
    cout << sum2(v.begin(), v.end()) << endl;

    int x2[10] = { 1,2,3,4,5,6,7,8,9,10 };
    // cout << sum2(x2, x2 + 10) << endl;  // ERROR
    // int는 어떻게 하나 ? int타입은 value_type을 지원하는가 ?

    int x3[10] = { 1,2,3,4,5,6,7,8,9,10 };
    cout << sum3(x3, x3 + 10) << endl;  // OK
    /* 반복자가 일반 포인터일 때도 처리할 수 있도록 iterator_trait을 2개 정의해 주는 걸 생각할 수있다.
       (1) 객체형 (2) 포인터형
       다만, iterator_traits 2개는 기 STL에서 제공하기 때문에, 그냥 사용하면 된다.
    */
}


auto의 사용 (C++11버전 부터 가능)
#include "show.h"

/*
int main()
{
    int x[3] = { 1,2,3}; // int를 double로 바꾸면 아래 n의 타입도 바꿔줘야 하는 문제가 있다.
    int n = x[0];

    vector<int> v = { 1,2,3};
   
    vector<int>::value_type n1 = v.front(); // 여기도
    vector<int>::iterator p = v.begin(); // 여기도
}
*/
int main()
{
    int x[3] = { 1,2,3 };
   
    // C++ 11 auto : [컴파일] 시 우변의 타입을 조사해서 좌변에 결정해 달라.
    auto n = x[0];

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

    auto n1 = v.front();
    auto p = v.begin();
}

/*
template<typename T> typename iterator_traits<T>::value_type
sum3(T first, T last)
{
//typename iterator_traits<T>::value_type s = 0; // 이것도... 아래와 같이 바꿀 수 있음.
auto s = *first;
++first;

while (first != last) {
s = s + *first;
++first;
}
return s;
}
*/


단위 전략
#include "show.h"
#include <set> // STL의 set, RB tree입니다. 균형 잡힌 트리.

/*
아래와 같이 하면, 오른차순 밖에 안됨.
template <typename T> class set2
{
public:
    void insert(const T&a)
    {
        if ( 기존 요소 > a)
            왼쪽에 넣기
        else
            오른쪽에 넣기
    }
};

그래서
less는 비교를 하기 위한 함수객체이다. 오른 차순.
greater는 내림 차순.
template <typename T, typename Pred = less<T>> class set3
{
   Pred pred; // 비교 정책을 담은 함수 객체

public:
void insert(const T&a)
{
   if (pred(기존요소, a))
     왼쪽에 넣기
   else
     오른쪽에 넣기
}

};

*/
int main()
{
    set<int> s;
    set<int, greater<int>> s2; //greater : > 연산 함.
    set<int, greater<int>> s3; //greater : > 연산 함.

    s.insert(10);
    s.insert(15);
    s.insert(20);
    s.insert(13);

    // set반복자를 ++하면 inorder 방식(정렬 유지)로 순회
    show(s);

    return 0;
}



정책 클래스
#include "show.h"


/* STL string의 원리
template <typename T,
          typename Traits = char_traits<T>,
          typename Alloc = allocator<T>>
class basic_string
{
    T*buff;
public:
    bool operator == (const basic_string& s) {
        if (Traits::compare(buff, s.buff, strlen(buff))
            return true;
        return false;
    }
};

typedef basic_string<char> string;
typedef basic_string<wchar_t> wstring;

우리가 쓰고 있던 string은 typedef이다.
*/

/* [정책 클래스] : 특정 클래스가 사용하는 다양한 정책을 담은 클래스.
                 비교 정책, 메모리 할당 정책 등.
                모든 정책 클래스는 지켜야할 규칙이 있다.

    string의 비교 정책 클래스 : compare, qe, lt 등의 static 함수가 있어야 한다.
    > C++ 표준 문서에 작성해야 할 함수 목록이 나와 있음. 다 만들기 귀찮으면 상속 받는다.
*/

class MyCharTraits : public char_traits<char>{
public:
    static bool eq(char a, char b) { return toupper(a) == toupper(b); }
    static bool lt(char a, char b) { return toupper(a) < toupper(b); }
    static bool compare(const char* a, const char* b, int sz) {
        return _memicmp(a, b, sz); // 대소문자 비교 않고 메모리 비교 할 때 씀.
    }
};

typedef basic_string<char, MyCharTraits> cstring;

int main()
{
    cstring s1 = "abcd";
    cstring s2 = "ABCD";

    if (s1 == s2)
        cout << "같은 문자열" << endl;
    else
        cout << "다른 문자열" << endl;

}

메모리 정책 변경
#include "show.h"

/*
정책 클래스를 이용한
메모리 할당 정책 변경 방법 ?

allocator 는 STL 정의 된 녀석 new, delete
*/
template<typename T, typename Ax = allocator<T>> class vector2
{
    Ax ax;
public:
    void resize(int sz)
    {
        /* 메모리 재할당이 필요 합니다.
           new / malloc / calloc / pool / system call ???
        */
        T* p = ax.allocator(sz);

        ax.deallocate(p);
    }
};

int main()
{
    vector<

}


MyAlloc
#include "show.h"

//cafe.naver.com/cppmaster --> C++ 관련 > 표준라이브러리게시판 >
// STL 표준 위원회 위원장 코드
template <class T> class MyAlloc {
public:
    // type definitions
    typedef T        value_type;
    typedef T*       pointer;
    typedef const T* const_pointer;
    typedef T&       reference;
    typedef const T& const_reference;
    typedef std::size_t    size_type;
    typedef std::ptrdiff_t difference_type;
    // rebind allocator to type U
    template <class U> struct rebind {
        typedef MyAlloc<U> other;
    };
    // return address of values
    pointer address(reference value) const {
        return &value;
    }
    const_pointer address(const_reference value) const {
        return &value;
    }

    /* constructors and destructor
    * - nothing to do because the allocator has no state
    */
    MyAlloc() throw() { }
    MyAlloc(const MyAlloc&) throw() { }
    ~MyAlloc() throw() { }
    template <class U> MyAlloc(const MyAlloc<U>&) throw() {}

    // return maximum number of elements that can be allocated
    size_type max_size() const throw() {
        return std::numeric_limits<std::size_t>::max() / sizeof(T);
    }

    // allocate but don't initialize num elements of type T
    pointer allocate(size_type num, const void* = 0) {
        // print message and allocate memory with global new
        std::cerr << "allocate " << num << " element(s)"
            << " of size " << sizeof(T) << std::endl;
        pointer ret = (pointer)(::operator new(num*sizeof(T)));
        std::cerr << " allocated at: " << (void*)ret << std::endl;
        return ret;
    }
    // initialize elements of allocated storage p with value value
    void construct(pointer p, const T& value) {
        // initialize memory with placement new
        new((void*)p)T(value);
    }
    // destroy elements of initialized storage p
    void destroy(pointer p) {
        // destroy objects by calling their destructor
        p->~T();
    }
    // deallocate storage p of deleted elements
    void deallocate(pointer p, size_type num) {
        // print message and deallocate memory with global delete
        std::cerr << "deallocate " << num << " element(s)"
            << " of size " << sizeof(T)
            << " at: " << (void*)p << std::endl;
        ::operator delete((void*)p);
    }

};

int main()
{
    vector<int, MyAlloc<int>> v;
    v.resize(10); // int 크기 10개 메모리 할당 필요.
}


컨테이너 규칙
#include "show.h"
#include <stack>

/* 1. sequence container : vector 연속 메모리, list 비연속, deque 연속 + 비연속

 3가지 컨테이너의 함수가 거의 동일 함.
 push_back
 insert

 단 하나의 차이가 있음.
 vector는 push_front가 없음 : 왜냐면, 벡터는 앞에 넣으면 성능 저하가 매우 큼.
                              때문에 아예 안만듬.

 기능은 동일하나, 성능이 다름.

*/

/*
STL 컨테이너 함수 규칙

규칙 1. 함수 이름이 거의 동일하다.
규칙 2. 제거와 리턴을 동시에 하는 함수는 [거의] 없다. (예외 안정성 보장을 위해)

* 예외 안정성 보장


*/
int main() {
    vector<int> v;
    v.push_back(10);

    int n1 = v.back(); // 리턴만. 제거 안됨
    //int n2 = v.pop_back(); // 제거만, 리턴 안됨. // error void 리턴;

    stack<int> s;
    s.push(10);
    s.push(20);

    cout << s.top() << endl; // 20. 꺼내기만 함.
    cout << s.top() << endl; // 20. 꺼내기만 함.
    s.pop(); // 제거만 함.
    // 멀티 쓰레드 할 때는 top과 pop을 하나로 동기 묶어줘야 해서 좀 귀찮음.

    return 0;
}

#include "show.h"

/* vector, deque : 임의 접근 반복자
   list : 양방향 반복자
  
   3개 컨테이너의 사용법은 거의 유사 함.
*/
int main() {
#if 0
    // 1. 생성. list, deque도 같음.
    vector<int> v1;
    vector<int> v2(10); // 디폴트 0으로 초기화 됨.
    vector<int> v3(10, 3);
    vector<int> v4(v3);
    vector<int> v5 = { 1, 2, 3 }; //c++11부터 가능.

    // 2. 요소 추가
    v1.push_back(10);
    //v1.push_front(10); // vector는 지원 안함. list, deque만 지원. 성능 문제로.
    v1.insert(v1.begin() + 1, 20);

    // 3. 제거
    v1.pop_back();
    v1.erase(v1.begin());
    v1.erase(find(v1.begin(), v1.end(), 10), v1.end());

    // 4. 요소 접근
    v1.front() = 40;
    v1[0] = 40; // []와 at은 vector와 deque만 가능. list는 안됨.
    v1.at(0) = 40;
    /*
   
    */
#endif

    vector<int> v8(10);
    try {
        //v8.at(11) = 20;
        v8[11] = 20;
    }
    catch (out_of_range &e) // out_of_range는 예외 클래스. at은 잘못된 인덱싱에 대해 예외 발생 시킴. []은 예외 발생 없음. 단 []가 빠름.
    {
        cout << "예외 발생" << endl;
    }

    for (int i = 0; i < v8.size(); i++) {
        v8.at(i) = 0; // 1
        v8[i] = 0; // 2가 더 낫다. 이경우는. for loop에서 v8.size()검사를 하고 있기 때문에 인덱스가 잘못될 가능성이 없다.
    }

   
}

vector와 string만 적용되는 개념 : capacity
#include "show.h"

int main()
{
    vector<int> v(10);
    v.resize(5);
   
    cout << v.size() << endl;   // 5
    cout << v.capacity() << endl; // 10

    v.push_back(5);
    cout << v.size() << endl; // 6
    cout << v.capacity() << endl; // 10

    v.shrink_to_fit();
    cout << v.size() << endl; // 6
    cout << v.capacity() << endl; // 6

    v.push_back(1);
    cout << v.size() << endl; // 7
    cout << v.capacity() << endl; // ?? 보통은 기존 크기의 1.5배를 키움.(논문) 9가 됨. 다만, compiler에 따라 다름.

    v.reserve(100); // capacity만 미리 확보.
    cout << v.size() << endl;
    cout << v.capacity() << endl;
}


사용자 정의 타입과 STL 컨테이너
#include "show.h"

/*
디폴트 생성자가 있는 것이 좋다.
*/

class People
{
    string name;
    int age;
public:
    People(string n, int a) :name(n), age(a) { }
};

int main()
{
    // 1. 생성
    //vector<People> v(10); // Error. 디폴트 생성자 필요.
    vector<People> v(10, People("Unknown", 0)); // OK
   
    //v.resize(20); // Error
    v.resize(20, People("u", 0)); // OK

    // 2. 요소 넣기
    People p1("park", 20); // 생성 후 유지 됨
    v.push_back(p1);

    v.push_back(People("park", 20)); // 임시객체로 처리하면 statement끝에서 메모리 해제됨.

    // 위 2가지는 어쨌든 2개의 객체가 생긴다.

    // 더 나은 방법. 생성자 인자만 전달해서 People 자체를 vector가 만들게 하자.
    // 객체 생성이 한번만 일어난다 !
    v.emplace_back("lee", 30); // C++11부터 가능. // perfect forwarding 기술 이용.

    //v.emplace_front(), v.emplace() 등 insert_front()와 insert등을 대체 함.
}


사용자 정의 타입의 소팅을 위한 함수객체
#include "show.h"

/*
사용자 정의 타입과 STL 컨테이너
1. 디폴트 생성자가 있는 것이 좋다.

*/
class PeopleAgeCmp;

class People
{
    string name;
    int age;
   
public:

    People(string n, int a) :name(n), age(a) { }

    // vector에 넣기 위해서는 크기 비교 함수가 필요 없지만,
    // sort사용시에는 구현 필요. 상수 함수를 만드는 것이 좋다.
    bool operator<(const People&p) const { return age < p.age; }
    bool operator==(const People&p) const { return age == p.age; }

    friend class PeopleAgeCmp;
};

class PeopleAgeCmp{
public:
    inline bool operator()(const People&a, const People &b) { return a.age == b.age; }
};

#if 0
class People
{
    string name;
    int age;
public:
    People(string n, int a) :name(n), age(a) { }

    // vector에 넣기 위해서는 크기 비교 함수가 필요 없지만,
    // sort사용시에는 구현 필요. 상수 함수를 만드는 것이 좋다.
    bool operator<(const People&p) const { return age < p.age; }
    bool operator==(const People&p) const { return age == p.age; }
};
#endif


int main()
{
    vector<People> v;
    v.emplace_back("kim", 20);
    v.emplace_back("aaa", 30);
    v.emplace_back("bbb", 10);

    //sort(v.begin(), v.end()); // ERROR 크기 비교 불가
    //sort(v.begin(), v.end()); // 기본적으로 <와 ==로 비교하고 있다. --> operator<와 operator==정의 한다.
    sort(v.begin(), v.end(), PeopleAgeCmp()); // 기본적으로 <와 ==로 비교하고 있다. --> operator<와 operator==정의 한다.

    return 0;
}


less (<)와 eq(==)만 정의하면 되는 이유
#include "show.h"

/*
사용자 정의 타입과 STL 컨테이너
1. 디폴트 생성자가 있는 것이 좋다.

[상수 객체]는 [상수 함수]만 부를 수 있다.
C++에서는 상수 함수를 반드시 써야 한다.
*/

using namespace std::rel_ops; // relational operators의 약자.
class People
{
    string name;
    int age;

public:

    People(string n, int a) :name(n), age(a) { }

    // vector에 넣기 위해서는 크기 비교 함수가 필요 없지만,
    // sort사용시에는 구현 필요. 상수 함수를 만드는 것이 좋다.
    // 2개만 만들고 rel_ops 추가하면 된다.
    bool operator<(const People&p) const { return age < p.age; }
    bool operator==(const People&p) const { return age == p.age; }
};

/*
원리
namespace std
{
    namespace rel_ops
    {
        template<typename T> inline bool operator> (const T &a, const T&b)
        {
           return !(a<b) && !(a==b);
        }
    }
}

*/

int main()
{
    People p1("aaa", 10);
    People p2("aaa", 15);

    bool b1 = p1 < p2; // ?? > OK
    bool b2 = p1 > p2; // ?? > NG > using namespace std::rel_ops;를 추가해 주면 OK
    /* >를 만든 적은 없지만, C++STL에 전역으로 >가 정의되어 있음. 거기에서 우리가 정의한 <와 ==를 사용 함.*/

    return 0;
}


컨테이너를 담는 컨테이너가 좋을 때가 많음.
#include "show.h"
#include <fstream>
/*
vector 안에 string 넣는 것.
둘다 container.
*/

bool foo(char a) { return a == 'a' || a == 'e' || a == 'i' || a == 'o' || a == 'u'; }

int main()
{
    ifstream fin("3_컨테이너8.cpp");

    string s;
    vector<string> v;
    while (getline(fin, s)) // fin에서 한줄 꺼내기
        v.push_back(s);

    // ------------
    //reverse(v.begin(), v.end()); // 상하 뒤집기

    for (int i = 0; i < v.size(); i++) {
        //reverse(v[i].begin(), v[i].end()); // 좌우 뒤집기. 첫줄.
        //replace(v[i].begin(), v[i].end(), 'i', ' '); // i를 공백으로 치환

        //  모든 모음을 공백으로
        replace_if(v[i].begin(), v[i].end(), foo, ' ');
    }
       
    // ------------


    for (int i = 0; i < v.size(); i++)
        cout << v[i] << endl;
}


C++11 컨테이너 forward_list (singly linked list)
#include "show.h"
#include <forward_list>

/*
  선형 컨테이너 : vector, list, deque - 1998년에 만들어짐. C++98에 표준화 됨.

  forward_list
  단, singly linked list는 없었음. 표준이 아니라 컴파일러 제조사마다 각자 slist라는 이름으로 제작해 제공.
  그래서 c++11에서는 forward_list라는 이름으로 지음.

  //unordered_set (실제는 해시. 기 벤더들이 hash라는 이름을 사용함)


*/
int main() {
    forward_list<int> s;

    s.push_front(10);

    show(s);
}

C++11 컨테이너 array
#include "show.h"
#include <array>
/*
  
   배열과 벡터 : 성능 5% 정도 차이밖에 안남. heap(벡터)과 stack(배열)의 차이.
   c++11에서 배열과 완전히 동일한 성능을 보장하는 컨테이너 만듬.

   사이즈 완전 동일하며, size() 기능 제공 : array 컨테이너
  
template<typename T, int N> struct array
{
    T buff[N];
    inline int size() const { return N; }

    typedef T value_type;
    typedef T* iterator; // 연속된 메모리 이동.

    inline iterator begin() { return buff; }
    inline iterator end() { return buff+N; }
};
*/

int main() {
    int x[10]; // 크기 고정.
    vector<int> v(10); // 크기 변경 가능.

    array<int, 10> ar = { 1, 2,3,4,5,6,7,8,9, 10 };
    cout << ar.size() << endl;
    show(ar);
}

C++11 컨테이너 tuple
#include "show.h"
#include <tuple>
/*

c++98 : vector, list, deque
c++11 : forward_list, array, tuple

tuple은 sorting 안됨.
용도는 여러개의 이질적인 데이터를 parameter 1개로 묶는다든가 할 때 사용.

typedef tuple<int, int> Point;

*/
int main()
{
    // 10개가 모두 int. 모두 같은 타임. homogeneous.
    vector<int> v(10);

    // 서로 다른 타입 보관. heterogeneous
    tuple<int, char, long, double, short> t(1, 'a', 3, 4.3, 7);
    get<0>(t) = 10;

    int n = get<0>(t);

    cout << n << ' ' << get<1>(t) << endl;

}

tuple 접근
#include "show.h"
#include <tuple>

template<typename T> void foo(T&a)
{
    //tuple에서 4번째(index 3) 것을 꺼내고 싶다.
    typename tuple_element<3, T>::type n = get<3>(a);

    cout << typeid(n).name() << endl; // double
}

int main()
{
    // 10개가 모두 int. 모두 같은 타임. homogeneous.
    vector<int> v(10);

    // 서로 다른 타입 보관. heterogeneous
    tuple<int, char, long, double, short> t(1, 'a', 3, 4.3, 7);
    get<0>(t) = 10;

    int n = get<0>(t);

    cout << n << ' ' << get<1>(t) << endl;
}


컨테이너와객체 복사
#include "show.h"

/*
   얕은 복사 문제
   > 해결책 : 깊은 복사 또는 참조 개수 사용
             또는 Move 생성자 사용
*/

class People
{
    char* name;
public:
    People(const char* n)
    {
        name = new char[strlen(n) + 1];
        strcpy(name, n);
    }

    People(const People&p)
    {
        cout << "복사 생성자 호출" << endl;
        name = new char[strlen(p.name) + 1];
        strcpy(name, p.name);
    }

    ~People() { delete[] name;}
};

int main()
{
    People p1("AAA");
    People p2 = p1;
}


C++11의 Move 생성자
#include "show.h"
/*
얕은 복사 문제

> 해결책 : 깊은 복사 또는 참조 개수 사용


*/

class People
{
    char* name;
public:
    People(const char* n = "AA")
    {
        name = new char[strlen(n) + 1];
        strcpy(name, n);
    }

    People(const People&p)
    {
        cout << "복사 생성자 호출" << endl;
        name = new char[strlen(p.name) + 1];
        strcpy(name, p.name);
    }

    // C++11의 Move 생성자 - 메모리 소유권 이전을 사용한 기술. &&가 2개 임.
    // move 생성자를 만들때는 예외가 없다고 표시 하는 것이 좋다.
    // 깊은 복사 생성자로 옴기면 원본 유지 보장됨. move 는 원본에 변화 생김.
    // 3개 이동은 OK하다가 4번째에서 예외 나면 ? 즉, exception safety 예외 안정성의 [강력 보장] 지원 못함.
    // 때문에 gcc 컴파일러는 개발자가 move 중 예외가 발생하지 않음을 보장해줘야 move 생성자 사용 가능.
    People(People&&p) noexcept // g++에서는 noexcept를 넣어 줘야 함.
    {
        cout << "move 생성자 호출" << endl;
        name = p.name; // 얕은 복사 후
        p.name = 0;    // 원본을 0으로.
    }

    ~People() { delete[] name; }
};

int main()
{
    //vector<People> v(10);
    //v.resize(20); // 원래 항목들은 어떻게 되나 ? > 복사 생성자 10번 불림. 그때마다 똑같은 메모리 생성 및 복사 발생. 부하 발생.

    // 그래서 그냥 이전 항목을 재사용할 수 있는 Move 생성자 도입.
    //People p1("AA");
    //People p2 = p1; //복사
    //People p3 = move(p1); // Move

    // Move 생성자가 있으면 Move 생성자를 쓰고, 없으면 복사 생성자를 사용 함.
    // v2.resize(20)할 때, 20개 영역 중 10개는 새로 생성되고, 10개는 move 생성자 사용.
    vector<People> v2(10);
    v2.resize(20);
}


연관 컨테이너 set
#include "show.h"
#include <set> // RedBlack Tree : Balanced Tree.

int main()
{
    set<int> s;

    s.insert(10);
    s.insert(30);
    s.insert(20);
    s.insert(40);
    set<int>::iterator p = s.begin(); // 가장 왼쪽 노드
    while (p != s.end())
    {
        cout << *p << endl;
        ++p; // inorder 순회. 정렬은 오름차순.
    }

    set<int, greater<int>> s2;
    s2.insert(10);
    s2.insert(30);
    s2.insert(20);
    s2.insert(40);
    set<int>::iterator p2 = s2.begin();

    typedef set<int, greater<int>> SET; //으로 간략화 가능. 또는 auto 사용(c++11)

    // set은 중복을 허용하지 않음.
    pair<SET::iterator, bool> ret = s2.insert(20); // 기존 항목을 가리키는 반복자, 두번째는 성공 여부.
    cout << ret.second << endl;
    if (ret.second == false)
        cout << "기존에 요소가 있습니다." << endl;
   
    // 아래 코드를 평가해 봅시다.
    SET::iterator p5 = find(s2.begin(), s2.end(), 20); // find는 선형 검색 ++. 결과는 나오지만 성능이 좋지 않음 !
    // tree 선형 검색 대신 이진 검색이 좋음.
    cout << *p5 << endl;

    // 대신 아래를 쓴다.
    SET::iterator p6 = s2.find(20); // 이진 검색.
}

연관 컨테이너 map
#include "show.h"

#include <map> // pair를 저장하는 set. key값으로 data 보관. RB Tree 임.

int main()
{
    map<string, string> m;

    //요소 넣기
    pair<string, string> p1("mon", "월요일");
    m.insert(p1);
    m.insert(make_pair("tue", "화요일"));
    m["wed"] = "수요일";
   
    cout << m["wed"] << endl; // 수요일
    cout << 'd' << m["fri"] << 'e' << endl; // "fri"가 없을 경우, 새롭게 만들게 된다. 즉, m.insert(make_pair("fri", ""));
   
    // 존재를 검사 할 때는 []를 쓰면 안된다.
    // 키값 조사 방법

    map<string, string>::iterator p = m.find("wed");
    if (p == m.end())
        cout << "thr 가 없습니다." << endl;
    cout << (*p).first << " " << (*p).second << endl; // 반복자 : 요소를 가르키는 포인터 역할.
}


컨테이너 아답터
#include "show.h"
#include <deque>

/*
list가 있는데, stack이 필요하다.(가정)

1. stack을 다시 만들자
2. list 한쪽으로 사용하면 stack이다. list를 재사용하자.

*/

/*
  Adapter 패턴 : 기존 클래스의 인터페이스(함수이름)를 변경해서
                 다른 클래스처럼 보이게 하는 기술.
*/
template<typename T> class stack :public list<T>
{
public:
    inline void push(const T&a) { list<T>::push_back(a); }
    inline void pop()           { list<T>::pop_back(); }
    inline T    top() { return list<T>::back(); }
};

template<typename T> class stack2
{
    list<T> st;

public:
    inline void push(const T&a) { st.push_back(a); }
    inline void pop() { st.pop_back(); }
    inline T    top() { return st.back(); }
};


template<typename T, typename C = deque<T>> class stack3
{
    C st;
public:
    inline void push(const T&a) { st.push_back(a); }
    inline void pop() { st.pop_back(); }
    inline T    top() { return st.back(); }
};

int main()
{
    stack<int> s;
    s.push(10);
    s.push(20);

    cout << s.top() << endl;
    s.pop();
    cout << s.top() << endl;

    s.push_front(30); // 사용자 실수 가능.
    // 못쓰게 하는 방법은 ?

    // 상속을 포함으로 바꾼다.
    // 상속은 구현 + 인터페이스도 상속하게 된다. stack
    // 포함은 [구현]만 재사용 한다. stack2
    // 상속보다는 포함이 더 좋은 디자인 가능하다.

    // 저장소까지 generic하게 하자
    stack3<int, vector<int>> s3;
    stack3<int> s4;

    // stack은 다른 자료 구조에 의존하는 존재.
    // 때문에 STL에서는 stack을 "stack adapter"라고 부름.

    // stack이 사용하는 메모리 할당자를 바꾸고 싶다면, 의존하는 container의 할당자를 바꾸면 된다.
    // stack<int, list<int, MyAlloc<int>>> s5;

    // Java는 stack을 vector의 자식으로 만듬?
}


#include "show.h"

// STL에는 3개의 컨테이너 어답터가 있음 : stack, queue, priority_queue
#include <stack>
#include <queue>

int main()
{
    // 다음중 에러는 ?
    stack<int, vector<int>> s1;
    stack<int, list<int>> s2;

    queue<int, vector<int>> s3; // vector는 앞쪽이 막힘. ERROR. Queue는 양쪽이 뚫려야 함.
    s3.push(10); // 뒤로 넣는다.
    s3.pop(); // 앞으로 제거.

    queue<int, list<int>> s4;

    priority_queue<int> pq;
    pq.push(10);
    pq.push(20);
    pq.push(5);

    priority_queue<int, vector<int>, greater<int>> pq2;
    pq2.push(10);
    pq2.push(30);
    pq2.push(5);

    //cout << pq.top() << endl; // 우선 순위가 제일 높은 요소가 나온다.
}


비트셋
#include "show.h"
#include <bitset> // 비트를 관리하기 위한 자료 구조

int main()
{
    bitset<32> bs;
    cout << sizeof(bs) << endl;

    bitset<40> bs2;
    cout << sizeof(bs2) << endl; // 4 바이트 단위로 커지는 것으로 보임.
   
    bs.set(); // 모두 1로
    bs.reset(); // 모두 0으로
    bs.set(2);// 2번째 비트만 1
    bs.flip(1); // 1번째 비트 뒤집기

    string s = bs.to_string();
    unsigned long n = bs.to_ulong();

    cout << s << endl;
    cout << n << endl;
}



난수 만들기
#include "show.h"

#include <bitset>
#include <ctime>

// unique한 난수 만들기

template <int N> class URandom {
    bitset<N> bs;
    bool recycle;

public:
    URandom(bool b = false) : recycle(b)
    {
        bs.set(); // 모든 비트를 1로
        srand(time(0));
    }

    int operator()() {
        if (bs.none()) // 모두 0인 경우
        {
            if (recycle == true)
                bs.set();
            else
                return -1;
        }

        int v = -1;
        while (!bs.test(v = rand() % N));
        bs.reset(v);
        return v;
    }
};

int main() {
//    int n  = rand() % 10;
// int n2 = rand() % 10;

    URandom<10> r(true);
    for (int i = 0; i < 15; i++)
        cout << r() << endl;
}


pair, make_pair
#include "show.h"

/*
Point x, y
Complex a, b

template<typename T, typename U> struct pair
{
    T first;
    U second;
};

pair : c++ 98
tuple : c++ 11
*/

pair<int, double> foo(pair<int, double> p)
{
    return p;
}

int main()
{
    pair<int, double> p(1, 3.4);
    foo(p);

    foo(pair<int, double>(1, 3.4)); // 임시 객체 전달 시. 다만 복잡함.
   
    foo(make_pair(1, 3.4));  // 함수 탬플릿으로 클래스 탬플릿 객체 생성 !!!

    pair<int, pair<int, int>> p3;
    pair<int, pair<int, pair<int, int>>> p4; // pair는 임의의 개수를 저장 가능... 다만 복잡함.
   
    //make_tuple(); 튜플도 있다.
    return 0;
}

3가지 스트림
#include <iostream> // 표준 입출력
#include <fstream>  // 파일 입출력
#include <sstream>  // 메모리 스트림

#include <string>

/*
  c++에는 스트림이 3개
  표준, io, sstream
*/
using namespace std;

/*
int main()
{
    string s;
    //cin >> s; // 표준 입력

    ifstream fin("10_스트림.cpp");
    //fin >> s; // 파일 입력

    istringstream iss("i am a boy"); // 메모리 스트림
    while(iss >> s)
      cout << s << endl;


    return 0;
}
*/

int main()
{
    int n = 10;
    //cout << "n = " << n;

    ostringstream oss;
    oss << "n = " << n; // C에서는 sprintf 사용. sprinf는 메모리 넘침 문제 있음.

    string s = oss.str();
    cout << s << endl;
}


[예외 안정성] 보장

// STL은 제거와 리턴을 동시에 하는 함수는  없다.
#include "show.h"

/*
   exception safety
   1. 완전 보장 : 예외가 없다. int a = 0; (절대 실패 할 수 없음. H/W 실패가 아니라면..)
   2. 강력 보장 : 예외가 있어도 처리하면 객체는 이전 상태를 보장한다. 사용가능. (아래 stack은 강력 보장 안됨)
   3. 기본 보장 : 예외가 있어도 자원 누수는 없다. 단, 객체의 상태는 보장 안됨. 사용 불가.

   STL의 설계 철학은 [2. 강력 보장이다]. 아래 stack은 2를 지키지 못한다.
*/
template<typename T> class stack
{
    T buff[10];
    int index;

public:
    stack() : index(0) {}
    void push(const T&a) { buff[index++] = a; }
    T pop() {
        --index;
        if (index < 0)
            throw 1;

        return buff[index];
    }
};

int main() {
    stack<int> s;
    try {
        s.pop();
    } catch (int n) {

    }

    /* exception 발생해도 프로그램은 계속 실행 가능.
       그런데 s객체를 사용할 수는 없음...
       s의 index는 이미 --해서 잘못된 값임. s를 다시 쓸 수가 없음.
    */
}



// [예외 안정성] 보장
#include "show.h"

// 아래도 강력 보장이 안됨.
template<typename T> class stack
{
    T buff[10];
    int index;

public:
    stack() : index(0) {}
    void push(const T&a) { buff[index++] = a; }
    T pop() {
        if (index < 1)
            throw 1;

        --index;
        return buff[index];
    }
};

int main() {
    stack<People> s;
    People p;
    s.push(p);

    try {
        // People p2 = s.pop();  // 이 코드는 실제 2가지가 실행됨.
        // 1. pop() 실행  - 문제 없음.
        // 2. People의 복사 생성자 실행. 복사 생성자 과정에서 메모리 문제로 실패 하면 ?
        // 실제 쓰지 못한 상황이지만, stack에서 항목은 사라짐.
       
        // 때문에 아래와 같이 바꿈.
        People p3 = s.top(); // 복사
        s.pop(); // 제거
    }
    catch (... n) {

    }
}


예제
#include "show.h"
#include <map>
#include <sstream>
#include <fstream>
using namespace std;

// 인덱스 만드는 프로그램.
// readme.txt
int main()
{
    ifstream fin("readme.txt");
    string s;
    int line = 0;

    typedef map<string, list<int>> IndexMap;
    IndexMap index;

    while (getline(fin, s)) {
        ++line;
        istringstream iss(s);
        string word;
        while (iss >> word) {
            index[word].push_back(line);
        }
    }

    // map 내용 출력
    ostream_iterator<int> out(cout, ", ");
    IndexMap::iterator p = index.begin();
    while (p != index.end()) {
        cout << p->first << " : ";
        copy(p->second.begin(), p->second.end(), out);
        cout << endl;
        ++p;
    }
}


예제2
#include "show.h"
#include <map>
using namespace std::placeholders;

void foo(void* p) { cout << "foo" << (int)p << endl; }
void goo(void* p, int n) { cout << "goo" << (int)p << endl; }

class CallbackTable {
    typedef function<void(void*)> HANDLER;
    map<string, vector<HANDLER>> table;
public:
    void Register(string ev, HANDLER h) {
        table[ev].push_back(h);
    }
    void Fire(string ev, void *data) {
        vector<HANDLER> &v = table[ev];
        for (int i = 0; i < v.size(); i++)
            v[i](data);
    }
};

int main()
{
    CallbackTable ct;

    ct.Register("DISCONNECT_WIFI", &foo);
    ct.Register("DISCONNECT_WIFI", bind(&goo, _1, 0));
    ct.Register("LOW_POWER", &foo);

    ct.Fire("DISCONNECT_WIFI", (void*)30); // 등록된 함수의 인자로 30 전달.
}

C++ STL 수업 - 1일차

ICore 강민석 대표님 강의.

Generic 프로그래밍
#include <iostream>
using namespace std;

char * xstrchr(char *s, char c)
{
    while (*s != 0 && *s != c)
        ++s;

    return *s == 0 ? 0 : s;
}

int main()
{
    char s[] = "abcdefghijk";
    char *p = xstrchr(s, 'c');

    if (p == 0)
        cout << "찾을 수 없습니다." << endl;
    else
        cout << *p << endl;
}



#include <iostream>
using namespace std;

// 검색 구간을 일반화 - 부분 문자열 검색이 가능하게 하자
// 일반화(Generic) 프로그래밍 : 하나의 함수(클래스)를 최대한 활용 범위를 넓혀서 만들자는 개념.

char * xstrchr(char *f, char *l, char c)
{
    while (f != l && *f != c)
        ++f;

    return f == l ? 0 : f; // 끝조건은 검사하지 않는다.
}

int main()
{
    char s[] = "abcdefghijk";
    char *p = xstrchr(s, s+5, 'c');

    if (p == 0)
        cout << "찾을 수 없습니다." << endl;
    else
        cout << *p << endl;


}


#include <iostream>
using namespace std;

// 3 단계 : 검색 대상의 타입을 일반화 > 템플릿 도입
// 문제 1: 결과 값이 반드시  입력과 같아야 함.
//           구간의 타입과 요소의 타입이 연관 됨. dobule의 배열에서 int를 찾을 수 없음.
// 문제 2: T*라고 하면 진짜 포인터만 사용해야 함. 스마트 포인터를 사용할 수 없음.

template<typename T>
T * xfindchr(T *f, T *l, T c)
{
    while (f != l && *f != c)
        ++f;
    return f == l ? 0 : f; // 끝조건은 검사하지 않는다.
}

int main()
{
    double s[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 ,10 };
    double *p = xfindchr(s, s + 10, 5);

    if (p == 0)
        cout << "찾을 수 없습니다." << endl;
    else
        cout << *p << endl;


}



#include <iostream>
using namespace std;

// 4 단계 :

template<typename T1, typename T2> T1 xfindchr(T1 f, T1 l, T2 c)
{
    while (f != l && *f != c)
        ++f;

    return f == l ? 0 : f; // 끝조건은 검사하지 않는다.
}

int main()
{
    double s[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 ,10 };
    double *p = xfindchr(s, s + 10, 5.0);

    if (p == 0)
        cout << "찾을 수 없습니다." << endl;
    else
        cout << *p << endl;
}

STL 컨테이너
#include <iostream>
using namespace std;

// xfind는 이동과 참조를 포인터 연산 ++, *를 사용
// 반면 slist는 링크 이동과, data 필드를 이용.
// 둘 사이를 잇기 위해 ++와 *를 override하는 iterator개체 사용.

template <typename T> struct Node
{
    T data;
    Node * next;
    Node(const T& a, Node *n) : data(a), next(n) {}
};

template <typename T> class slist_iterator
{
    Node <T>* current;
public:
    inline slist_iterator(Node<T>* p = 0) : current(p) {}

    // 이 객체는 포인터 처럼 동작해야 함 (++ , * , ==, != 연산이 필요 함)
    inline slist_iterator & operator++() {
        current = current->next;
        return *this;
    }
    inline T& operator*() {return current->data;    }
    inline bool operator==(const slist_iterator &it) { return current == it.current;}
    inline bool operator!=(const slist_iterator &it) { return current != it.current;}
};

template <typename T> class slist
{
    Node<T> * head;
public :
    slist() : head(0) {}

    // Node의 생성자를 잘 활용해서 slist의 앞에 넣는 코드.
    void push_front(const T& a) { head = new Node<T>(a, head); }

    // 모든 컨테이너 설계자는 반드시 자신의 시작과 마지막 다음(0)을 가르키는 반복자를 리턴하는 함수 2개가 있어야 한다.

    slist_iterator<T> begin() { return slist_iterator<T>(head); }
    slist_iterator<T> end() { return slist_iteraor<T>(0); }
};

int main()
{
    slist<int> s;
    s.push_front(10);
    s.push_front(20);
    s.push_front(30);
    s.push_front(40); //  이때의 메모리 그림 page6.

    slist_iterator<int> p = s.begin();
    while (p != s.end()) {
        cout << *p << endl;
        ++p;
    }
}


반복자 (Iterator)
#include <iostream>
using namespace std;

// 싱글 링크드 list를 생각해 봅시다.

template <typename T> struct Node
{
    T data;
    Node * next;
    Node(const T& a, Node *n) : data(a), next(n) {}
};

template<typename T> class slist
{
    Node<T> * head;
public:
    slist() : head(0) {}

    // Node의 생성자를 잘 활용해서 slist의 앞에 넣는 코드.
    void push_front(const T& a) { head = new Node<T>(a, head); }
};

int main()
{
    slist<int> s;
    s.push_front(10);
    s.push_front(20);
    s.push_front(30);
    s.push_front(40); //  이때의 메모리 그림 page6.
}


#include <iostream>
using namespace std;

// xfind는 이동과 참조를 포인터 연산 ++, *를 사용
// 반면 slist는 링크 이동과, data 필드를 이용.
// 둘 사이를 잇기 위해 ++와 *를 override하는 iterator개체 사용.
template<typename T1, typename T2> T1 xfind(T1 f, T1 l, T2 c)
{
    while (f != l && *f != c)
        ++f;

    return f;
}

template <typename T> struct Node
{
    T data;
    Node * next;
    Node(const T& a, Node *n) : data(a), next(n) {}
};

template <typename T> class slist_iterator
{
    Node <T>* current;
public:
    inline slist_iterator(Node<T>* p = 0) : current(p) {}

    // 이 객체는 포인터 처럼 동작해야 함 (++ , * , ==, != 연산이 필요 함)
    inline slist_iterator & operator++() {
        current = current->next;
        return *this;
    }
    inline T& operator*() { return current->data; }
    inline bool operator==(const slist_iterator &it) { return current == it.current; }
    inline bool operator!=(const slist_iterator &it) { return current != it.current; }
};

template <typename T> class slist
{
    Node<T> * head;
public:
    slist() : head(0) {}

    // Node의 생성자를 잘 활용해서 slist의 앞에 넣는 코드.
    void push_front(const T& a) { head = new Node<T>(a, head); }

    // 모든 컨테이너 설계자는 반드시 자신의 시작과 마지막 다음(0)을 가르키는 반복자를 리턴하는 함수 2개가 있어야 한다.
    typedef slist_iterator<T> iterator;

    iterator begin() { return iterator(head); }
    iterator end() { return iterator(0); }
};

int main()
{
    slist<int> s;
    s.push_front(10);
    s.push_front(20);
    s.push_front(30);
    s.push_front(40); //  이때의 메모리 그림 page6.

    slist<int>::iterator p2 = xfind(s.begin(), s.end(), 30);
    cout << *p2 << endl;

    slist<int>::iterator p = s.begin();
    while (p != s.end()) {
        cout << *p << endl;
        ++p;
    }
}


반복자 사용
#include <iostream>
#include <list>
#include <vector>
#include <string>
#include <algorithm> // find()  같은 일반 함수가 이 헤더에 정의. 흔히 "알고리즘"이라고 함.

using namespace std;

int main()
{
    string s = "hello world";

    string::iterator p1 = s.begin();
    ++p1;
    cout << *p1 << endl;
   
    typedef list<int> Container;

    Container s1 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; //C++11문법

    // s1 반복자 꺼내서 전부 출력
    // s1 뒤집어 보기. reverse()
    // s1 출력

    Container::iterator si = s1.begin();
    while (si != s1.end())
    {
        cout << *si << endl;
        ++si;
    }

    reverse(s1.begin(), s1.end());

    si = s1.begin();
    while (si != s1.end())
    {
        cout << *si << endl;
        ++si;
    }
}


반복자 무효화
#include "show.h"

int main() {
    vector<int> v(10, 3);  // 10개를 3으로 초기화

    vector<int>::iterator p = v.begin();
    cout << *p << endl;

    //v.resize(20); // 이 순간 반복자는 무효화 됨. 사용하면 안됨.

    v.resize(5); // 크기가 줄어들때는 무효화 되지 않음.
    cout << v.size() << endl;
    cout << v.capacity() << endl;
    cout << *p << endl;
}


반복자가 다를 수 밖에 없는 이유

#include "show.h"
#include "slist.h"

int main() {
    slist<int> s;
   
    s.push_front(10);
    s.push_front(20);

    slist<int>::iterator p = s.begin();
    ++p; // OK
    --p; // 자료 구조 특성상 지원 불가능...
}

반복자 종류
#include "show.h"
#include "slist.h"

// 반복자 분류 : 반복자가 지원하는 operator에 따라  (category)
// 1. 입력  : = *p, ++ operator 지원. one pass 만 보장 함.
// 2. 출력   : *p =, ++ operator 지원. one pass 만 보장 함.

// multi pass 보장함.
// 3. 전진형 : 입출력, ++ operator 지원 ==> 싱글리스트
// 4. 양방향 : 입출력, ++   -- operator 지원==> 더블리스트
// 5. 임의접근 : 입출력, ++, --, [], +, - operator 지원 ==> 연속된 메모리와 유사 하나 항상은 아님. p = p+5 , p = p -3.
//               vector, deque(일부는 연속되어 있지는 않지만, 연속된 경우는 성능 효과 있으므로, + - 연산 지원함)
// 6.  c++11에서 반복자의 분류를 추가 합니다.
//    continuous 반복자 : 연속된 메모리 보장할 때만 사용. (배열)

// 입력 / 출력은 프로그램의 관점에서 본다.
// 프로그램 --> container는 출력 *p =
// container--> 프로그램 입력 = *p

// stl은 최초의 interface만 제공.

// 어떤 알고리즘이 어떤 operator를 지원하는지 정확히 알고 있어야 빌드 오류가 안남.
// 또한 반복자가 제공하는 operator를 이용하여 다른 알고리즘이나 멤버 함수를 사용하여 목표를 달성할 수는 있음.

int main() {
    int x[10] = { 1,2,3,4,5,6,7,8,9,10 };

   
}


copy 알고리즘
#include "show.h"

int main() {
    int x[10] = { 1, 2,3,4,5,6,7,8,9, 10 };
    vector<int> v(10, 0);

    copy(x, x + 10, v.begin()); // x ==> v에 복사

    show(v);
}

스트림 반복자
#include "show.h"

int main() {
    ifstream fin("7_stream_iter.cpp"); // 입력 파일
    ofstream fout("7_stream_iter_cped.cpp");

   //istream_iterator<char> p1(fin), p2; // p2 디폴트 생성자는 EOF
    istreambuf_iterator<char> p1(fin), p2; // white space 포함 해서 입력
    ostream_iterator<char> p3(fout, "");
   
    copy(p1, p2, p3);
}

int min3() {
    ifstream fin("7_stream_iter.cpp"); // 입력 파일
    //istream_iterator<char> p1(fin), p2; // p2 디폴트 생성자는 EOF
    istreambuf_iterator<char> p1(fin), p2; // white space 포함 해서 입력
    //cout << *p1 << endl;
   
    ostream_iterator<char> p3(cout, "");
    copy(p1, p2, p3);
    return 0;
}

int min2() {
    istream_iterator<int> p(cin); // p는 cin의 입력 버퍼를 가르키는 반복자

    int n = *p;

    cout << n << endl;
    return 0;

}

int min() {
    int x[10] = { 1, 2,3,4,5,6,7,8,9, 10 };
    ostream_iterator<int> p(cout, " "); // p 는 출력 버퍼를 가르키는 반복자

    *p = 10; // cout << 10 // 출력 후 자동으로 증가(++p) 되는 특징이 있음.
    *p = 20;

    copy(x, x + 10, p);
    return 0;
}



#include "show.h"
#include <iterator>
#include <fstream>

int main() {
    ifstream fin("7_stream_iter.cpp"); // 입력 파일
    ofstream fout("7_stream_iter_cped.cpp");

   //istream_iterator<char> p1(fin), p2; // p2 디폴트 생성자는 EOF
    istreambuf_iterator<char> p1(fin), p2; // white space 포함 해서 입력
    ostream_iterator<char> p3(fout, "");
   
    copy(p1, p2, p3);
}

int min3() {
    ifstream fin("7_stream_iter.cpp"); // 입력 파일
    //istream_iterator<char> p1(fin), p2; // p2 디폴트 생성자는 EOF
    istreambuf_iterator<char> p1(fin), p2; // white space 포함 해서 입력
    //cout << *p1 << endl;
   
    ostream_iterator<char> p3(cout, "");
    copy(p1, p2, p3);
    return 0;
}

int min2() {
    istream_iterator<int> p(cin); // p는 cin의 입력 버퍼를 가르키는 반복자

    int n = *p;

    cout << n << endl;
    return 0;

}

int min() {
    int x[10] = { 1, 2,3,4,5,6,7,8,9, 10 };
    ostream_iterator<int> p(cout, " "); // p 는 출력 버퍼를 가르키는 반복자

    *p = 10; // cout << 10 // 출력 후 자동으로 증가(++p) 되는 특징이 있음.
    *p = 20;

    copy(x, x + 10, p);
    return 0;
}


함수 탬플릿을 이용한 클래스 탬플릿 생성
#include "show.h"
// 클래스 템플릿은 암시적 추록이 되지 않으므로, 사용시 복잡해 보인다.
// 암시적 추론이 가능한 함수 템플릿을 사용해서 클래스 탬플릿을 생성한다.

#if 0
template<typename T>
back_insert_iterator<T> back_inserter(T&c)
{
    return back_insert_iterator<T>(c);
}
#endif

int main()
{
    int x[5] = { 1, 2,3,4,5 };
    list<int> s(5, 0); // 5개를 0으로 초기화

    //copy(x, x + 5, s.begin()); // 기존 5개에 대해 새로 노드를 추가가 아니라, 덮어쓰기다.

    // s의 끝에 추가 하는 방업 3가지
    // 1. push_back 사용
    //for (int i = 0; i < 5; i++)
        //s.push_back(x[i]);

    // 2. 후방 삽입 반복자 사용
    //back_insert_iterator<list<int>> p(s); // p는 s의 끝에 추가하는 반복자.
    //*p = 30; // s.push_back(30)와 같은 의미.
    //copy(x, x + 5, p);

    //3. 후방 삽입 반복자 만드는 함수 사용.
    //copy(x, x + 5, back_inserter(s));



    show(s);
}

#if 0
<int>를 없앨 수 있나 ?

template <typename T> T square(T a) { return a*a; }
square(3); // 컴파일러가 T결정. 암시적 추론. OK
square<int>(3); // 사용자가 T결정. 명시적 추론. OK

list<int>s(5, 3); // c++에서는 클래스는 <int> 제거시 암시적 추론 불가능. 현재 지원 논의 중.
#endif


함수 버전 삽입 반복자
#include "show.h"

int main()
{
    list<int> s(5, 0);

    int x[5] = { 1,2,3,4,5 };

    // 덮어쓰기
    //copy(x, x + 5, s.begin());

    // 끝에 추가하기
    //copy(x, x + 5, back_inserter(s));

    // 앞에 추가하기
    //copy(x, x + 5, front_inserter(s)); // front_insert_iterator<> 생성

    // 중간에 넣기. s의 2번째 넣기
    //copy(x, x + 5, inserter(s, ++s.begin()));

    // 임의 삽입 통한 앞에 넣기
    copy(x, x + 5, inserter(s, s.begin()));
    // 클래스 버전 : insert_iterator<T>
}

역 반복자

#include "show.h"

// reverse_iterator의 원리
#if 0
template <typename T> class reverse_iterator
{
    T &p;
public:
    reverse_iterator(T&t) : p(t) { {}
   
    reverse_iterator& operator++() {
        --p;
        return *this;
    }
   
    reverse_iterator& operator--() {
        ++p;
        return *this;
    }
}
#endif

int main()
{
    list <int> s = { 1,2,3,1,2,3, 4, 5 };
    //list<int>::iterator p = s.begin();
    //list<int>::reverse_iterator p = s.rbegin();
    //list<int>::const_iterator p = s.cbegin();
    //list<int>::const_reverse_iterator p = s.crbegin();
    //*p = 20;
    //++p;
    //cout << *p << endl;

    list<int>::iterator p = find(s.begin(), s.end(), 2);
    cout << *p << endl;
    ++p;
    cout << *p << endl;

    list<int>::reverse_iterator p2= find(s.rbegin(), s.rend(), 2);
    cout << *p2 << endl;
    ++p2;
    cout << *p2 << endl;
}

함수

#include "show.h"

/*
알고리즘에 인자로 함수가 전달되는 경우가 많음
단항 함수 : 인자가 한개 인 것
이항 함수 : 인자가 2개 인 것
*/

bool foo(int a) { return (a % 4) == 0; }

int main()
{
    list<int> s = { 1,2,3,8,7,6, 12 };

    //1. s에서 처음 나오는 3을 찾아 보기
    //list<int>::iterator p1 = find(s.begin(), s.end(), 3); // 상수 검색 버전
    //cout << *p1 << endl;
   
    //2. s에서 처음 나오는 4의 배수를 찾기
    list<int>::iterator p2 = find_if(s.begin(), s.end(), foo);
    //s.begin()에서 s.end()까지 foo에게 보내서 foo를 참으로 되는 경우를 p2에 보냄

    cout << *p2 << endl;
}

함수 객체
#include "show.h"

/*
함수 객체 : 괄호() 연산자를 재정의해서 함수처럼 사용하는 것을 함수객체라고 함.

장점
1. 객체이므로 상태를 가질 수 있다. 일반 함수는 상태가 없다.
   상태와 생성자를 잘 활용하면 재사용성이 좋아진다.
2. 인라인 치환된다.
   find_if(s.begin(), s.end(), foo); // foo함수가 다시 호출될 때, 함수 포인터로 전달 한 경우이므로 inline 치환 안됨. 느리다.

   module m(4);
   find_if(s.begin(), s.end(), m); // m함수 객체가 다시 호출될 때 inline 치환됨. 더 빠름.
*/
struct modulus {
    int value;
    modulus(int n) : value(n) {}

    bool operator()(int a) { return a%value == 0; }
};

int main()
{
    modulus m(3); // 생성자 호출 value를 3으로 초기화.
    bool b = m(4); // 생성자가 아니라 m.operator()(4)로 실행됨.
    cout << b << endl;

    /*
    a+b; // a.operator+(b);
    a-b; // a.operator-(b);
    a(); // a.operator()();  a.operator()에서 ()연산자 이름. a.operator()()의 두번째 ()는 함수 호출.
    a(1); // a.operator()(1);

    */
}


함수 객체의 장점
(1) 인라인 치환 되어 성능 빠름 (2) 내부 상태를 가질수 있음

#include "show.h"

int foo(int a, int b) {
    cout << "foo" << endl;
    return a + b;
}


struct Plus{
    inline int operator()(int a, int b) { return a + b; }
};

template <typename T>
struct PlusT {
    inline T operator()(T a, T b) { return a + b; }
};


int main()
{
    int x[5] = { 1,2,3,4,5 };
    int y[5] = { 1,2,3,4,5 };

    vector<int> v(5, 0);

    // 일반 함수를 알고리즘 인자로 보내면 인라인 치환 안된다. 느리다.
    //transform(x, x + 5, y, v.begin(), foo);

    //함수 객체를 만들어 넘기면 인라인 치환된다. 빠르다.
    //PlusT<int> p;
    //transform(x, x + 5, y, v.begin, p);

    plus<int> p2;
    transform(x, x + 5, y, v.begin(), p2);

    show(v);
}


알고리즘에 조건 전달 방법 3가지
#include "show.h"
int foo(int a, int b) {
    cout << "foo" << endl;
    return a + b;
}

int main() {
    int x[5] = { 1,2,3,4,5 };
    int y[5] = { 1,2,3,4,5 };

    vector<int> v(5, 0);
    // 1. 일반함수
    // transform(x, x+5, y, v.begin(), foo);

    // 2. 함수객체
    //transform(x, x + 5, y, v.begin(), plus<int>()); // 클래스 이름() : 임시객체 생성. 문자의 끝(;)에서 파괴 됨.

    // 3. C++11의 람다 표현식 문법 ==> 일반함수와 달리 inline 치환됨. g++은 4.9.x vc++ 2013이상.
    transform(x, x + 5, y, v.begin(), [](int a, int b) {return a + b;}); // [] 람다 인트로듀서.
    show(v);
}

바인더
#include "show.h"

int main()
{
    modulus<int> m;    // %연산. 2항 함수 개체.
    int n = m(10, 3); // 10%3을 함. --> 1
    cout << n << endl;

    int x[10] = { 1,2,3,4,5,6,7,8,9,10 };

    // 3의 배수를 찾고 싶다.
    //int *p = find_if(x, x + 10, m ); // ??? find_if의 세번째 인자는 1항 함수여야 한다. 반면 m는 입력으로 2항을 필요로 함. //error
   
    // 이항 함수 객체단항 함수 객체로 변경하는 도구.
    binder2nd<modulus<int>> f(m, 3); // m의 2번째 인자를 3으로 고정한 단항함수 f
    cout << f(10) << endl;

    int *p = find_if(x, x + 10, f); 
    cout << *p << endl;


    // binder헬퍼 : binder2nd<>를 만들어 주는 헬퍼 함수
    int *p = find_if(x, x + 10, bind2nd(m,3)); // bind2nd(m,3) : m의 2nd 인자를 3으로 고정한 걸 쓰겠다.  // inline 함수라 빠르다.
}


#include "show.h"
using namespace std::placeholders; //_1, _2, _3


void foo(int a, int b, int c, int d)
{
    printf("%d %d %d %d\n", a, b, c, d);
}
int main()
{
    modulus<int> m;

    cout << m(10, 3) << endl;

    // bind1st(), bind2nd() : 이항함수 객체를 단항 함수객체로... c++11에서 deprecated.
    // 이항 함수 객체에 대해서만 가능. 3항 이상에서는 안됨.
    cout << bind2nd(m, 3)(10) << endl; // 10 % 3
    cout << bind1st(m, 3)(2) << endl; // 3 % 2  // m의 1st 인자를 3으로 고정.

    // c++11에서는 3항이상 및 함수 객체 뿐만 아니라 함수도 bind 지원한다.
    // M항을 N항으로 고정할 수 있다.
   
    cout << bind(m, _1, 3)(10) << endl; // m(10,5) 10%5
    cout << bind(m, 10, _1)(3) << endl; // m(10, 3) 10%3

    cout << bind(m, 10, 3)() << endl; // m(10, 3) 10%3

    bind(&foo, 3, _2, 2, _1)(9, 7); // foo는 함수객체가 아닌 함수. 3, 7, 2, 9
    return 0;
}


STL 함수 포인터 객체
#include "show.h"
using namespace std::placeholders; //_1, _2, _3
void foo(int a, int b, int c, int d) {    printf("%d %d %d %d\n", a, b, c, d); }
void goo(int a, int b) { cout << "goo" << endl; }
void hoo(int a)        { cout << "hoo" << endl; }

int main()
{
    void (*f1)(int) = &hoo;
    //f1 = &goo; // 인자 개수 signature가 달라서 error

    // STL 의 범용적 함수 포인터 객체
    function<void(int)> f = &hoo; // 함수
    f(10); // hoo(10);

    f = bind(&goo, _1, 7);
    f(10); // goo(10,7);

    f = bind(&foo, 2, 8, _1,  1);
    f(3);  // 2, 8, 3, 1
    return 0;
}


함수와, 함수의 포인터 사용 차이.
  class Dialog{
  public :
     void Close() {}
  };

  void foo(){}

  int main()
  {
     int a; // a는 int. &a는 int *이다.
     void (*f1)() = &foo; // OK
     void (*f2)() = foo; // OK. 하지만, "함수가 함수 주소로 암시적 형변환 되어서 대입되고 있음." 예외적 문법.  C언어의 특징.

     void(Dialog::*f3)() = &Dialog::close; // OK
     void(Dialog::*f3)() = Dialog::close; // NG. 멤버 함수 이름은 함수 포인터로 암시적 형변환이 안됨. 반드시 주소를 붙여야 함. C++언어의 특징.
  }









배열의 타입
#include "show.h"

int main()
{
    int n = 10; // n의 데이터 타입은 int.
    double d = 3.4; // d의 데이터 타입은 double.

    int* p = &n; // 주소를 꺼내려면 &사용.
                 // 포인터 변수를 만들려면 변수 이름앞에 * 표시.

    int x[5] = { 1, 2, 3, 4, 5 }; // x의 타입은 int[5].
   
    //변수의 선언에서 이름을 빼면 그 변수의 타입이다.

    // x의 주소를 꺼내서 p2에 담아 보세요.
   

    int *p2 = x; // 배열의 주소가 아님. 배열의 이름은 배열의 1번째 요소의 주소로 암시적 형변환 됩니다.
                 // c++ 표준 문서 4-2. Array to Pointer Conversion 문법.

    int(*p3)[5] = &x; //  이 정확한 배열의 주소 표현. 즉 선언에서 이름을 제거하고 포인터로 바꾸면 됨. []이 *보다 순위가 높아서 ()로 묶어 줄 필요 있음.

    printf("%p %p\n", p2, p2 + 1); // 몇 바이트 차이 나는지 확인
    printf("%p %p\n", p3, p3 + 1);

    int y[2][2] = { {1,1}, {2, 2} };
    int(*p4)[2][2] = &y; // OK
   
    int(*p5)[2] = y;

}

객체의 초기화

#include "show.h"

int main()
{
    int n1 = 10;
    int n2 = n1;
    // 모든 객체는 자신과 동일한 타입으로 초기화 가능 하다.
    //단, 자신으로 타입으로 초기화 할 수 없는 것은(1) 배열(2) 함수
    int &r = n1;

    int x[5] = { 1, 2, 3,4,5 };
    //int y[5] = x; // NG :
    //대신 1번째 요소의 주소로 형변환 될 수 있다.
    //모든 변수는 참조 변수를 만들며 초기화 될 수있다.

    int *p = x; //OK

    int(&r2)[5] = x; // OK
}

배열의 타입
#include "show.h"

template<typename T> void foo(T a)
{
    cout << typeid(T).name << endl;
}

template<typename T> void goo(T& a)
{
    cout << typeid(T).name << endl;
}

int main()
{
    int n = 10;
    double d = 3.4;
    int x[5] = { 1, 2, 3, 4, 5 };

    foo(n); // T == int
    foo(d); // T == double
    foo(x); // T ?? int a[5] = x; ==> 복사 문제 발생. 대신 int *a = x가 된다. 즉, T는 int*

    goo(x); // T ?? int a[5] = x;는 안됨. 단, int(&a)[5] = x;는 가능.
}

배열의 타입 2
template<typename T> void foo(T a, T b)
{
}

template<typename T> void goo(T & a, T & b)
{
}

int main()
{
    foo("apple", "banana"); // 문자열은 원래 정체가 배열. 다만, 함수 호출 시 포인터로 형변환 됨.
    goo("apple", "banana"); // 참조로 보내면, 진짜 배열이 넘어감. char[6], char[7] 2개의 다른 타입이 넘어감. 그래서 ERROR.
    goo("apple", "banna"); // 참조로 보내면서 둘다 char[6], char[6]여서 이건 ERROR가 안남.
}


show.h
#pragma once
#include <iostream>
#include <list>
#include <vector>
#include <string>
#include <algorithm>
#include <iterator>
#include <fstream>
#include <functional>
using namespace std;

template<typename T> void show(T& c)
{
    typename T::iterator p = c.begin();
    while (p != c.end()) {
        cout << *p << " ";
        ++p;
    }
    cout << endl;
}

2016년 6월 13일 월요일

Weave Architecture ?

[libweave의 third_party 라이브러리들]

1. chromium
    크로미엄의 base 폴더 포함. Bind, String, RefCount, JsonParser 기능

2. googletest
    구글 Unit test
 
3. libevhtp
    libevent의 http API 제공 라이브러리. 작은 웹서버.

4. libuweave
    https://weave.googlesource.com/weave/libuweave/

    Weave is an open standard for secure device setup and message passing which is described in detail on the [Google Weave homepage] (https://developers.google.com/weave/).

uWeave (pronounced “micro weave”) is an implementation of the Weave protocol intended for use on microcontroller based devices. This is in contrast to the “full size” Weave library which is for devices capable of running a full Linux environment.
Like the rest of Brillo and Weave, the uWeave codebase is open source. uWeave is distributed under a BSD-style license available in the LICENSE file.
The uWeave effort is relatively new. The repository you see here is still in the early stages of development and not yet suitable for production use. If that doesn't scare you away, the Getting Started guide can help you figure out how to build and use the uWeave library.

5. modp_b64
    MODP_B64 - High performance base64 encoder/decode


아키텍처는 ?

2016년 6월 6일 월요일