이번 시간에는 STL의 스마트 포인터, , 에 대해 살펴보겠습니다.

Smart pointer

스마트 포인터의 개념

  • 포인터와 유사하게 동작하는 추상화된 타입으로 포인터의 기능 외에 자동화된 자원관리 등의 기능을 추가로 제공한다.

Shared_ptr

  • 헤더 ->
  • 자원을 공유하며 참조 count가 0이 될 때 동적 할당된 메모리가 해제됨
int main()
{
//    int a = 0; // copy initialization/
//    int a(0);  // direct initialization

    //shared_ptr<Car> p = new Car; // error
    shared_ptr<Car> p(new Car);  // ok
    shared_ptr<Car> p1(new Car);
    cout << sizeof(p1) << endl;

//    shared_ptr<Car> p2 = p1;

}
  • 커스텀 deleter 전달 가능 (함수, 함수 객체, 람다 가능)
    • 할당자 생략 가능
int main()
{
    shared_ptr<Car> p( new Car,
                      [](Car* p) { delete p; },
                     MyAlloc<Car>() );
}
  • shared_ptr과 배열(C++17 이후 삭제자를 제공하지 않아도 safe함)
    • C++17 이후 배열 첨자 연산 가능
int main()
{
    shared_ptr<Car[]> p1( new Car[10]); //  delete[],   []연산있음

    p1[0].Go();
}
  • 기본 연산
#include <iostream>
#include <memory>
#include "Car.h"
using namespace std;

int main()
{
    shared_ptr<Car> p1( new Car ); //1

    p1->Go();  // Car 의 멤버 접근.

    Car* p = p1.get();
    cout << p << endl;

    shared_ptr<Car> p2 = p1; // 2
    cout << p1.use_count() << endl; // 2

    //p1 = new Car; // error
    p1.reset( new Car); // ok
    p1.reset();
    p1.swap(p2);

}
  • new 직접 할당시 메모리 할당이 2번 발생(제어블록, 객체)
    • 해결방법 -> make_shared
void* operator new( size_t sz)
{
    cout << "new sz : " << sz << endl;
    return malloc(sz);
}

int main()
{
    //shared_ptr<Car> p1( new Car ) ; // 비효율

    shared_ptr<Car> p1 = make_shared<Car>();
    shared_ptr<Car> p1 (make_shared<Car>());
    shared_ptr<Car> p1 = allocate_shared<Car>(MyAlloc<Car>() );
}
  • raw 메모리로 2개의 shared_ptr을 초기화 할 경우 문제 발생
int main()
{
    Car* p = new Car;

    shared_ptr<Car> sp1(p);  // 제어 블럭 생성.
    shared_ptr<Car> sp2(p);  // 제어 블럭 생성.

    shared_ptr<Car> sp3 = make_shared<Car>(); // make_shared 권장
}
  • enable_shared_from_this -> CRTP
    • this를 가지고 제어 블럭을 공유하는 shared_ptr을 만들 수 있게함
class Worker : public enable_shared_from_this<Worker> // CRTP
{
    Car c;
    shared_ptr<Worker> holdMe;
public:
    void Run()
    {
        //holdMe = this;
        //holdMe = p

        holdMe = shared_from_this();

        thread t(&Worker::Main, this);
        t.detach();
    }
    void Main()
    {
        c.Go();     // 멤버 data(Car) 사용
        cout << "finish thread" << endl;

        holdMe.reset();
    }
};

int main()
{
    {
        shared_ptr<Worker> sp = make_shared<Worker>();
        sp->Run();
    } // 여기서 sp 파괴를 시도함 but, shared_from_this로 막을 수 있음
    getchar();
}

Weak_ptr

  • shared_ptr의 상호 참조 이슈
struct People
{
    People(string s) : name(s) {}
    ~People() { cout << "~People : " << name << endl;}

    string name;
    shared_ptr<People> bf; // best friend
};

int main()
{
    shared_ptr<People> p1( new People("KIM"));
    shared_ptr<People> p2( new People("LEE"));

    p1->bf = p2;
    p2->bf = p1;
}
  • 상호 참조 이슈 해결방법 -> weak_ptr
    • weak_ptr은 참조 계수를 증가시키지 않음
int main()
{
    //shared_ptr<Car> wp; // use count 증가

    weak_ptr<Car> wp; // use count 증가 안함.


    shared_ptr<Car> sp( new Car );
    wp = sp;
    cout << sp.use_count() << endl; // 1

    if ( wp.expired() )
        cout << "destroy" << endl;
    else
    {
        cout << "not destroy" << endl;

        // weak_ptr을 사용해서는 대상객체에 접근할수 없다.
        //wp->Go(); // error.

        // weak_ptr을 가지고 다시 shared_ptr을 만들어야 한다.

        shared_ptr<Car> sp2 = wp.lock();

        if ( sp2 )
            sp2->Go();
    }
}
  • 대상 객체가 진짜 파괴 되는 시점
    • use count(shared_ptr 참조 카운트) == 0 && weak count(waek_ptr 참조 카운트) = 0 일 때

Unique_ptr

  • 공유 없이, 자원 독점, 자동 메모리 해제
int main()
{
    shared_ptr<Car> sp1(new Car);
    shared_ptr<Car> sp2 = sp1;  // ok. 참조 계수 2.

    auto f = [](Car* p) { delete p;}
    unique_ptr<Car, decltype(f)> up1(new Car, f); // 자원 독점

    unique_ptr<Car, void(*)(Car*)> up1(new Car, foo); // 자원 독점

    //unique_ptr<Car> up2 = up1; // error.

    cout << sizeof(sp1) << endl;
    cout << sizeof(up1) << endl;
}
  • 복사는 될 수 없지만, move는 가능
int main()
{
    unique_ptr<int> up1(new int);
    //unique_ptr<int> up2 = up1; // error.

    unique_ptr<int> up2 = move(up1); // ok. 
}
  • 삭제자 변경
void foo(int* p )
{
    cout << "foo" << endl;
    delete p;
}

struct Deleter
{
    void operator()(int* p ) const
    {
         delete p;
    }
};


int main()
{
    //shared_ptr<int> sp( new int, foo);

    // 1. 함수객체 사용
    // unique_ptr<int, Deleter> up( new int);

    // 2. 함수 포인터 사용
    // unique_ptr<int, void(*)(int*) > up( new int, foo);

    // 3. 람다 표현식 사용.
    auto f = [](int* p ) { delete p; cout << "lambda" << endl;};

    unique_ptr<int, decltype(f) > up( new int, f);

    unique_ptr<int[] > up( new int[10]);
}
  • shared_ptr와의 호환성 -> unique_ptr -> move -> shared_ptr 만 가능(나머지 X)
int main()
{
    shared_ptr<int> sp(new int);
    unique_ptr<int> up(new int);

    shared_ptr<int> sp1 = up;       // error
    shared_ptr<int> sp2 = move(up); // ok

    unique_ptr<int> up1 = sp;       // error.
    unique_ptr<int> up2 = move(sp); // error.
}

Chrono

ratio

  • 컴파일 시간 분수 값을 나타내는 템플릿
    • 분자, 분모는 약분된 상태로 저장됨
    • 실행 시간에 메모리에 보관하는 값은 없고, 컴파일 시간에 사용되는 상수
int main()
{
	ratio<2, 4> r1; // 2/4  => 1/2

	cout << sizeof(r1) << endl; // 1

	cout << r1.num << endl; // 분자
	cout << r1.den << endl; // 분모

	cout << ratio<2, 4>::num << endl;  // 1
	cout << ratio<2, 4>::den << endl;  // 2
}
  • ratio간 연산도 가능
int main()
{
    ratio_add< ratio<1,4>, ratio<2,4> > r2; // 3/4

    cout << r2.num << endl; // 3
    cout << r2.den << endl; // 4

    ratio<1, 1000> r3; // milli
    ratio<1000, 1> r4; // kilo

    milli m;
    kilo  k;
    cout << k.num << endl; // 1000
    cout << k.den << endl; // 1
}

duration

  • 헤더, ratio로 표현되는 단위(주기)에 대한 값을 보관하는 클래스
  • 오직 하나의 값만을 보관하며, 컴파일 타임에 연산됨
int main()
{
//    double distance = 3; // 3m, 3km, 3cm

    duration<double, ratio<1,1>> d1(3); // 3m
//    duration<double, ratio<1,1000>> d2(d1); // milli 3000
//    duration<double, ratio<1000,1>> d3(d1); // km

    duration<double, milli> d2(d1); // milli 3000
    duration<double, kilo> d3(d1); // km
    cout << d2.count() << endl; // 3000
    cout << d3.count() << endl; // 0.003

    using MilliMeter = duration<double, milli>;
    using KiloMeter = duration<double, kilo>;
    using Meter = duration<double, ratio<1,1>>;

    Meter m(3);
    KiloMeter km(m);

    cout << km.count() << endl; // 0.003
}
  • 주의사항, 형변환시 손실이 발생 할 수 있으므로 cast 필요
int main()
{
    using MilliMeter = duration<int, milli>;
    using KiloMeter  = duration<int, kilo>;
    using Meter      = duration<int, ratio<1,1>>;

    Meter      m(400);  // 600m
    MilliMeter mm(m);   // 600000    ok.. data 손실없다.
    //KiloMeter  km(m);   // 0.6 => 0 또는 1 error.

//    KiloMeter  km = duration_cast<KiloMeter>(m); // 버림.
    KiloMeter  km = round<KiloMeter>(m); // 반올림.

    cout << mm.count() << endl; // 600000
    cout << km.count() << endl; //
}

chrono

  • 시간 관련 타입 제공
int main()
{
    /*
    using seconds      = duration<int>; // duration<int, ratio<1,1>>
    using minutes      = duration<int, ratio<60,1>>;
    using hours        = duration<int, ratio<3600>>; // ratio<3600,1>
    using milliseconds = duration<int, milli>;
    */
    hours h(1);
    minutes m(h); // 60
    seconds s(h); // 3600

    cout << m.count() << endl;
    cout << s.count() << endl;

    //hours h2( s); // 1 error
    hours h2 = duration_cast<hours>(s);

    cout << h2.count() << endl;

    using days = duration<int, ratio<3600 * 24, 1>>;

    days d(1);

    minutes m2(d);

    cout << m2.count() << endl;  // 60 * 24
}
  • 초기화 방법 및 연산 방법
#include <iostream>
#include <chrono>
using namespace std;
using namespace chrono;


void foo( seconds s) {}  // seconds s = 3


int main()
{
    // seconds => duration<int, ratio<1,1>>

    seconds s1(3);  // ok
    //seconds s2 = 3; // error
    seconds s3 = 3s;// ok.   seconds operator""s(3)
    seconds s4 = 3min;

    cout << s4.count() << endl; // 180

    //foo( 3 ); // error.
    foo( 3s); // ok

    //this_thread::sleep_for( 3min )

    seconds s5 = 3min + 40s;

    cout << s5.count() << endl; // 220
}
  • 시간 구하는 방법
    • time_point : 기간의 시작과, 경과 개수를 나타내는 타입
    • epoch time : 1970년 1월 1일 0시를 기점으로 경과된 시간 단위
#include <iostream>
#include <chrono>
#include <string>
using namespace std;
using namespace chrono;

int main()
{

    system_clock::time_point tp = system_clock::now();

    // 1970년 1월 1일 0시 기준.
    nanoseconds ns = tp.time_since_epoch();

    cout << ns.count() << endl;

    hours h = duration_cast<hours>(ns);
    cout << h.count() << endl;

    // time_point => string
    time_t t = system_clock::to_time_t(tp);
    string s = ctime(&t);
    cout << s << endl; // 현재 날짜 출력
}

Function

bind

  • 참조로 bind 하는 방법
#include <iostream>
#include <functional>
using namespace std;
using namespace std::placeholders;

void f1(int a, int b, int c) { printf("f1 : %d, %d, %d\n", a,b,c);}
void f2(int& a)              { a = 20;}

int main()
{
    int n = 0;
    bind(&f2, ref(n))(); // cref : const 참조
    cout << n << endl; // 20 ?  0
}
  • 멤버 함수, 멤버 데이터 bind 하는 방법
#include <iostream>
#include <functional>
using namespace std;
using namespace std::placeholders;

class Test
{
public:
    int data = 0;

    void f(int a, int b) // void f( Test* this, int a, int b)
    {
        data = a;
        printf("f : %d, %d\n", a, b);
    }
};

int main()
{
    Test t;

    // 객체를 고정하는 경우
    bind(&Test::f, &t, 1, 2)(); // t.f(1,2)
    bind(&Test::f, t, 1, 2)(); // 복사본을 고정함
    bind(&Test::f, ref(t), 1, 2)(); // 참조

    // 객체를 인자로 전달하는 경우.
    bind(&Test::f, _1, 1, 2)(&t); // 참조
    bind(&Test::f, _1, 1, 2)(t); // 값


    bind(&Test::data, &t)() = 10; // t.data = 10

    cout << t.data << endl; // 1
}

function

  • 함수 포인터의 단점 -> signature가 동일한 함수만 담을 수 있음
  • std::function -> 함수 포인터를 일반화한 개념
  • 시그니처가 다를 경우 bind와 결합하여 사용 가능
#include <iostream>
#include <functional>
using namespace std;
using namespace std::placeholders;

void f1(int a )              { printf("f1 : %d\n", a);}
void f2(int a, int b, int c) { printf("f2 : %d, %d, %d\n", a,b,c);}


int main()
{
    //void(*f)(int) = &f1;
    //void(*f)(int) = &f2; // error

    function<void(int)> f;

    f = &f1; // ok
    f(10); // f1(10);

    f = bind(&f2,1, 2, _1); // f2를 대입하려면 bind 필요
    f(10); // f2(1,2,10);
}
  • member 함수와 function
#include <iostream>
#include <functional>
using namespace std;
using namespace std::placeholders;

class Test
{
public:
    int data = 0;

    void f(int a, int b)
    {
        data = a;
        printf("f : %d, %d\n", a, b);
    }
};

int main()
{
    Test t;

    // 1. 일반 함수 모양의 function
    function<void(int)> f1;

    f1 = bind(&Test::f, &t, _1, 20);

    f1(10); // t.f(10, 20)

    // 2. 객체를 function 의 인자로 받는 방법
    function<void(Test*, int)> f2;
    f2 = bind(&Test::f, _1, _2, 20);
    f2(&t, 100); // t.f(100, 20)
    function<void(Test, int)> f3;
    f3 = bind(&Test::f, _1, _2, 20);
    f3(t, 200); // t.f(200, 20)

    function<void(Test&, int)> f4;
    f4 = bind(&Test::f, _1, _2, 20);
    f4(t, 300); // t.f(300, 20)
    cout << t.data << endl; // 300
}
  • 멤버 변수와 function
#include <iostream>
#include <functional>
using namespace std;
using namespace std::placeholders;

class Test
{
public:
    int data = 10;
};

int main()
{
    Test t1, t2;

    // 1.
    function<int&()> f1;
    f1 = bind( &Test::data, &t1); // t1.data를 보관
    cout << f1() << endl; // t1.data . getter
    f1() = 20; // t1.data = 20
    cout << t1.data << endl; // 20

    // 2. 객체를 function 인자로 전달하는 방식

    function<int&(Test*)> f2;

    f2 = bind(&Test::data, _1);
    f2(&t1) = 20;
    f2(&t2) = 30;

    cout << t1.data << endl; // 20;
    cout << t2.data << endl; // 30;
}
  • 활용 예제
  
#include <iostream>
#include <string>
#include <functional>
#include <map>
#include <vector>
using namespace std;
using namespace std::placeholders;

class NotificationCenter
{
    using HANDLER = function<void(void*)>;

    map<string, vector<HANDLER>> notif_map;
public:
    void Register(string key, HANDLER h)
    {
        notif_map[key].push_back(h);
    }
    void Notify( string key, void* param)
    {
        vector<HANDLER>& v = notif_map[key];

        for( auto f : v)
            f(param);
    }
};

void f1(void* p) { cout << "f1" << endl;}
void f2(void* p, int a, int b) { cout << "f2" << endl;}

int main()
{
    NotificationCenter nc;
    nc.Register("CONNECT_WIFI", &f1);
    nc.Register("CONNECT_WIFI", bind(&f2, _1, 0, 0) );


    nc.Notify("CONNECT_WIFI", (void*)0);
}