티스토리 뷰

개요


리소스를 로딩한다거나 어떤 연산을 수행하기 위해 thread를 만들어서 동작시킬 수 있다. 하지만 thread가 언제 끝날지 보장할 수 없기 때문에 이것에 대해 방어적인 코딩이 필요하다.


예를 들어 리소스를 thread로 로딩하고 로딩이 완료될 때 마다 해당 인스턴스로 callback을 하여 리소스 로딩이 완료되었음을 알려주는 구현이라고 가정하자.

이 인스턴스는 자신이 요청한 thread가 완료될 때 까지 삭제하면 안되지만 thread가 언제 끝날지 보장할 수 없기 때문에 thread가 언제 끝나더라도 동작하도록 구현할 필요가 있다.


c++에 추가된 shared pointer를 사용하여 이를 구현해 보자.






unsafe callback with raw pointer


기존 pointer를 사용해서 구현할 경우 아래와 비슷한 흐름을 가지게 될 것이다.


class Character {
public:
void PrintItems() {
for each (int item in _items) {
std::cout << "item : " << item << std::endl;
}
}

void PushItem(int item) {
_items.push_back(item);
}

private:
std::vector<int> _items;

};

void LoadingThread(std::function<void()> callback) {
Sleep(1000);
callback();
}

void main() {
Character* pChar = new Character();
std::thread* loading;

pChar->PushItem(1);
pChar->PushItem(5);
pChar->PushItem(10);

auto callback = std::bind(&Character::PrintItems, pChar);

callback();

loading = new std::thread(LoadingThread, printItems);

delete pChar;
std::cout << "deleted pChar" << std::endl;

loading->join();
if (loading) delete loading;

}


위 경우 main에서 호출하는 callback()은 정상 동작한다.

하지만 LoadingThread가 1초 대기 후에 callback을 호출하는 반면 thread를 만들자 마자 바로 pChar를 메모리 해제하기 때문에 1초 후에 thread가 callback을 호출하면 접근 에러가 발생한다.

실제로 로딩중인 캐릭터가 맞아서 죽거나 view에서 벗어나서 지워야 할 경우 위와 같은 상황이 발생할 수 있다.

이는 raw pointer는 가리키고 있는 메모리가 해제되더라도 그것을 인지하지 못한 채 계속 해당 메모리를 가리키기 때문인데, 누군가 해당 메모리를 가리키고 있다면 그 메모리를 해제하지 못하도록 할 필요가 있다.




shared_ptr


reference count가 포함된 스마트 포인터로 ref count가 0이 되면 메모리에서 지우며, 복사하거나 대입할 경우 ref count가 증가하여 메모리 관리를 쉽게 할 수 있도록 해준다.




weak_ptr


shared_ptr로 변환할 수 있는 포인터로 shared_ptr로 생성된 객체를 복사하거나 가리킬 수 있지만 해당 객체의 ref count를 증가시키지 않는다. 따라서 shared_ptr 인스턴스의 life time에 전혀 영향을 주지 않으면서 접근할 수 있는 포인터이다.

weak_ptr은 객체에 직접 접근할 수 없으므로 반드시 shared_ptr로 변환하여 사용해야 하는데, 이 때 변환한 shared_ptr의 메모리 존재 여부를 검사할 수 있기 때문에 안전하게 사용할 수 있다.




thread safe callback with smart pointer


위 두 스마트 포인터를 사용해서 thread에 안전한 callback system을 구현하면 아래와 같은 흐름에서도 안전하게 동작할 수 있다.


void SafePrint(std::weak_ptr<Character> w) {
auto pChar = w.lock();
if (pChar) {
pChar->PrintItems();
}
}

void main() {
auto pChar = std::make_shared<Character>();
std::thread* loading;

pChar->PushItem(1);
pChar->PushItem(5);
pChar->PushItem(10);

auto callback = std::bind(&SafePrint, std::weak_ptr<Character>(pChar));

loading = new std::thread(LoadingThread, callback);

pChar.reset(); // delete pChar

std::cout << "deleted pChar" << std::endl;

loading->join();
if (loading) delete loading;

}


weak_ptr을 lock()으로 shared_ptr로 변환시킬 수 있는데, 이 때 shared_ptr이 가리키는 객체의 ref count가 main함수의 reset()으로 인해 0이 되었기 때문에 이미 메모리에서 해제된 상태이다.

shared_ptr은 메모리에서 해제시키면서 해당 영역을 NULL로 만들어주기 때문에 단순한 조건문으로 실제로 인스턴스가 메모리에 살아있는지 검사할 수 있다. 아직 메모리에 있을 경우 callback을 수행하며 이미 지워진 경우 callback 자체를 무시하도록 해서 안전하게 구현할 수 있다.






마치며


이 포스팅은 stack overflow에 올린 질문에 대한 답변을 기초로 작성한 것이다. [질문글]

원래 알고있던 포인터 개념이 raw pointer라고 불리며 구시대 유물처럼 여겨지는 것을 보고 좀 충격을 받았다. c++이 참 많이 변화하고 있는데 열심히 해서 빨리 따라잡아야겠다.



'Programming > C++' 카테고리의 다른 글

C++ - 메모리 해제  (0) 2017.12.30
C++ - 메모리 생성  (0) 2017.12.30
C++11 - std::bind  (0) 2017.09.21
콜백함수 만들기 : 함수 포인터(Function Pointer)  (0) 2017.02.05
C/C++ - sscanf, sscanf_s  (0) 2017.01.23
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함