C++ STL - Utility[6]
이번 시간에는 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);
}