The New C++: Smart pointers
Library TR
JTC1.22.19768 ISO/IEC TR 19768 - C++ Library Extensions1 문서에 나와있는 정보를 기초로 하여 작성된 것이다.
Smart pointers (shared_ptr, weak_ptr)
'Smart pointer'란 동적으로 할당된 object에 대한 pointer를 나타내는 object이다. C++ 프로그래머들이 흔히 가장 쉽게 떠올릴 수 있는 smart pointer의 기능이 바로 scope를 벗어나면 할당된 object를 해제해주는 것이고, 이는 바로, 이미 C++ standard에 포함되어있는 auto_ptr의 역할이다. 하지만, 이 auto_ptr는 semantic상 실용적으로 쓰이기에 치명적인 제약사항들이 있다.
일반적으로 auto_ptr에 pointer를 대입하고 scope가 끝나서 해제되는 것까지는 별로 문제가 없다. 하지만, auto_ptr이 다른 곳으로 이동할 때 문제가 된다. auto_ptr이 복사가 될 때, auto_ptr은 복사가 되는 곳 (destination)으로 pointer를 옮기고 원본(source)에서는 pointer의 흔적을 지워버린다. 다시 말하면, auto_ptr의 복사가 일어날 때, 실제로는 pointer의 이동이 일어나는 것이다. (이를 ownership의 이동이라고 표현한다) 이러한 copy 방식을 destructive copy라고 한다. auto_ptr의 destructive copy 방식은 source를 가진 쪽이 더이상 pointer를 사용할 필요가 없을 때, 즉 소유권(ownership)을 이전하고 싶을 때, 매우 유용하다. 예를 들면, dynamic하게 할당된 object를 caller쪽으로 return하고 싶은 경우가 있을 수 있다.
class SomeProtocol
{
public:
typedef std::auto_ptrResponsePtr;
ResponsePtr getResponse();
};void foo()
{
SomeProtocol::ResponsePtr respPtr = getResponse();
...
// scope가 끝나면 respPtr내에 들어있는 Response 객체는 자동으로 해제된다.
}
다른 한가지 예는, 다른 함수로 pointer를 전달하고 자신은 더이상 사용하고 싶지 않은 경우이다.
void foo(SomeProtocol::ResponsePtr ptr)
{
....
// scope가 끝나면 respPtr내에 들어있는 Response 객체는 자동으로 해제된다.
}void bar()
{
SomeProtocol::ResponsePtr respPtr = someProtocol.getResponse();
....
foo(respPtr);
....
// foo에 respPtr을 복사하고 나면, respPtr은 더이상 Response 객체를 가지지 않는다.
}
반면, 이러한 destructive copy policy 때문에 auto_ptr을 C++ standard library의 container에서 사용할 수 없게 된다. container들은 destructive copy semantic을 지원하지 않기 때문이다. (Assignable concept는 copy ctor 또는 assignment의 post condition으로, source와 target이 equivalent할 것을 명시하고 있다.) container에 auto_ptr을 넣는 데까지는 문제가 없다고 쳐보자. 하지만, container에서 값을 참조하기만 해도, container 내의 auto_ptr은 빈 auto_ptr이 되어버리는 것을 쉽게 상상할 수 있다. 물론 이렇게 하기 전에도 내부 동작에 의해 망가져버릴 가능성은 얼마든지 있다.
따라서, scope을 벗어났을 때, 자동으로 해제되면서도, (auto_ptr처럼), container에서도 사용할 수 있는 무언가가 당연히 표준에 있어야할 것 같다. 이것이 바로 TR1에 추가될 shared_ptr이다. (현재의 표준에는 없다는 얘기)
shared_ptr은 말그대로 shared ownership의 방식으로 동작하는 smart pointer이다. 즉, copy가 될 때, ownership을 공유한다는 것이다. 이러한 방식을 구현하는 가장 간단한 방식이 바로 reference counting이고, TR1 draft를 에서 counter(use_count())를 explicit하게 표현하는 것을 보면, reference counting을 하는 smart pointer라고 봐도 거의 무방할 듯 하다. 그리고, shared_ptr은 CopyConstructible, Assignable, LessThanComparable 이므로, 모든 표준 container에 들어갈 수 있다.
shared_ptr로 공유되는 pointer를 꺼내서 사용하고 싶은 경우도 당연히 존재하기 때문에, weak_ptr을 제공한다. weak_ptr은 말그대로 weak reference를 의미하는 것이고 따라서 무효화(nullify)될 수 있는 pointer라고 보면 된다. 단순히 pointer value를 꺼내서 사용할 경우에는 (shared_ptr이 모두 사라졌을 때) dangling pointer 문제가 발생할 수 있으나, weak_ptr은 무효화여부를 체크할 수 있기 때문에, dangling pointer 문제는 발생하지 않도록 보장해주는 역할을 한다고 볼 수 있다.
shared_ptr와 weak_ptr은 MT safety에 대해서 별로 신경을 쓰지 않는 것 같은데, 만약에 이것이 사실이라면 과연 shared_ptr은 MT app에서는 무용지물이 되는 것이 아닐까 약간 걱정이 된다. 그렇다고 해서, shared_ptr의 구현상에서 무조건 MT safety를 보장해주는 것은 또 performance overhead를 발생시킬 수도 있는 일이고 말이다.
이러한 문제를 그나마 깔끔하게 해결하고 있는 쪽이, policy-based design으로 ownership 정책, MT 지원 여부 등을 policy로 분리한 Loki2의 design이 아닐까 한다.
관심이 있는 사람들은 다음 링크를 참조할 것.
Tuple types (tuple)
CS를 전공한 사람이라면 discrete mathematics에서 tuple을 배웠을 것이다. tuple은 여러개의 object를 묶어서 표현할 수 있는 일종의 container이다.
현재의 표준에는 pair가 있어서 한 쌍의 object를 generic하게 표현할 수 있다. 하지만, n개의 묶음을 표현하기 위해서는 pair의 pair와 같은 꽁수를 쓰거나, 그 때마다 struct를 정의하거나 해온 것이 사실이다.
pair의 pair와 같은 방법을 사용할 경우에는 코드가 매우 깔끔하지 못하게 된다는 것을 쉽게 알 수 있다.
typedef std::pair
> MyTableEntry;
typedef std::vectorMyTable; MyTable myTable;
myTable.push_back(make_pair(1, make_pair("foo", true)));
MyTableEntry foundEntry = myTable.find(...);
std::cout << "(" << foundEntry.first << ", " << foundEntry.second.first << "," << foundEntry.second.second << ")" << std::endl;
3개짜리 tuple이었기에 망정이지 5개 정도 된다면 그야말로 악몽이다. 복잡한 코드는 버그의 원인이 된다.
struct를 정의하는 방법은 코드의 복잡성은 전혀 없다. struct는 단순한 object의 모음을 표현하는 데에 있어서, 어떤 개발자에게는 불필요한 기능들을 포함하고 있을 수도 있다. 그 외에도 struct는 tuple이 제공하는 여러 기능 (operator<(), make_tuple(), operator<<(ostream, ...))을 가지고 있지 않으므로 불편할 수 있다. 개인적으로는 struct type을 정의하는 순간부터 새로운 object (그것도 어설픈) 가 탄생하는 것 같아서 여러 struct들이 난무하는 상황은 꺼려지기도 한다. (C에서와는 완전히 다른 얘기다.)
다음 tuple의 template parameter를 보면, 더욱 확실한 감이 올 것이다. 단지 pair의 확장판이라고 봐도 거의 무방하다.
template
class tuple;
template parameter list를 보면 ...가 있어서 어떤 사람들은 의아해하겠지만, 이것은 새로 추가된 C++ 문법 같은 것이 아니다. 단순히, 구현에 따라 M개까지의 tuple을 지원하도록 하는 것을 의미한다. 당황스럽게도, 이것의 가장 단순한 구현 (boost의 구현)은 3개짜리 tuple, 4개짜리 tuple, ..., M개짜리 tuple을 각각 정의하는 것이다. 이러한 제약이 없는 tuple을 만들 수 없으냐고 한다면, 만들 수 있다. 역시 Loki2를 보면 Type들의 list를 표현하는 장치를 구현하고 있는데, 이를 이용하여, 제약없는 tuple을 만들 수 있다. 하지만, 실제로 100개짜리 tuple을 쓰는 개발자는 거의 없다고 봐도 무방하므로, tr1::tuple의 접근도 acceptable하리라고 본다.
관심이 있는 사람들은 다음 링크를 참조할 것.
Generalized function pointers (function)
관심이 있는 사람들은 다음 링크를 참조할 것.
Nearly complete C99 library compatibility
1 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2003/n1540.pdf
2 Modern C++ Design의 저자인 Andrei Alexandrecu가 만든 C++ library이다. http://sourceforge.net/projects/loki-lib/
'C & C++ 관련' 카테고리의 다른 글
extern "C" (0) | 2009.10.16 |
---|---|
Program Library HOWTO (0) | 2009.09.26 |
동적 적재(DL) 라이브러리 (0) | 2009.09.26 |