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;
}