사용 예 // example, not preferred style PassRefPtr<Node> createSpecialNode()
{
PassRefPtr<Node> a = new Node;
a->setSpecial(true);
return a;
}
RefPtr<Node> b = createSpecialNode();
WebKit에서는 모든 경우에 RefPtr을 사용하기를 권장한다. 따라서 위의 경우처럼 ownership을 전달하고자 하는 경우 // example, assuming new Node starts with reference count 0 PassRefPtr<Node> createSpecialNode()
{ RefPtr<Node> a = new Node;
a->setSpecial(true);
return a.release();
}
RefPtr<Node> b = createSpecialNode();
adoptRef
Reference count의 조작 없이 생성된 객체를 전달할 수 있게 한다. PassRefPtr의 friend로 되어있다.
사용 예 // preferred style PassRefPtr<Node> Node::create() { return adoptRef(new Node); } RefPtr<Node> e = Node::create();
// preferred style PassRefPtr<Node> createSpecialNode() { RefPtr<Node> a = Node::create(); a->setCreated(true); return a.release(); } RefPtr<Node> b = createSpecialNode();
OwnPtr, PassOwnPtr
RefPtr, PassRefPtr은 RefCounted에서 상속받은 클래스에만 적용할 수 있는 반면, OwnPtr, PassOwnPtr은 pointer type이나 pointed-to type을 취할 수 있다.
복사 생성자를 보면 인자로 std::auto_ptr을 받는데... 그렇다면 결국 패싱할때 소유권을 넘긴다는 말인데... 흠... 음... 흠... 좀더 살펴 봐야 겠다.
template <typename T> struct RemovePointer { typedef T Type; };
template <typename T> struct RemovePointer<T*> { typedef T Type; };
template <typename T> class OwnPtr : public Noncopyable { public: typedef typename RemovePointer<T>::Type ValueType; typedef ValueType* PtrType; // ...
여기서 객체가 대입이 되는데, 객체의 생성 시점에서 대입이 이루어 질 때는, copy constructor가 사용된다.
하지만, 이미 생성되어 있는 객체에 대입이 이루어 질 경우는 copy assignment가 사용된다.
따라서, copy assignment에서는 이미 생성되어 있는 객체를 cleanup 해주고 copy 해야 한다.
즉, Sample X(10); Sample Y(20); Sample Z = X; // copy constructor Z = Y; // copy assignment
세 가지 이상의 객체가 복사되는 경우가 있다.
1. 함수의 인자로 전달
2. 함수의 리턴 값
3. 예외 전달
const char * p = greeting;// 상수 데이터(char const * p = greeting;) char * const p = greeting;// 상수 포인터 const char * const p = greeting;// 상수 데이터, 상수 포인터
class Sample { public: Sample(char e) : _elem(e) {} const char* Get() const { return &_elem; } // 앞의 const는 return type이 const라는 것이고, 뒤의 const는 상수 멤버임을 의미한다. // 상수 멤버란, 어떤 멤버 함수가 그 객체의 어떤 데이터 멤버도 건드리지 않아야 함(static 멤버 제외).
private: char _elem; };
만약, char* Get() const { return &_elem; }
같이 한다면, 상수 객체에 대한 포인터를 리턴받아 값을 수정하는 것이 가능해 지므로 compile error.
const 키워드가 있고 없고의 차이만 있는 멤버 함수는 오버로딩이 가능하다. class TextBlock { public: ... // 상수 객체에 대한 operator[]
The WM_COMMAND message is sent when the user selects a command item from a menu, when a control sends a notification message to its parent window, or when an accelerator keystroke is translated.
WM_COMMAND가 받는 경우는
1. 사용자가 메뉴에서 command item을 선택했을 때 (메뉴)
2. 컨트롤이 부모 윈도우로 통지 메세지를 보낼 때 (컨트롤)
3. 엑셀레이터키가 해석될 때 이다. (Accelerator)
즉, 메뉴, 컨트롤, 엑셀러레이터 의 3가지의 경우이다.
일단 각각을 살펴보기 전에 MSDN에 있는 WM_COMMAND의 파라미터가 어떻게 해석되는가 하면
MSDN에 보면This function retrieves a message from the calling thread's message queue and places it in the specified structure. 라고 되어있다.
즉,쓰레드의 메세지 큐에서 가져오는 것이다.
그런데, 하나의 UI 쓰레드(메세지큐를 가질 수 있는 쓰레드)는 여러 개의 윈도우 프로시저를 가질 수 있는데, 어떤 윈도우 프로시저에 이 메세지를 전달 해야 할까...
GetMessag()는에 MSG구조체를 채워 주는데, 이 구조체가 typedef struct {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG, *PMSG;요렇게 생겨 먹었다.
MSDN에 보면 hwnd는 Handle to the window whose window procedure receives the message. hwnd is NULL when the message is a thread message. 라고 되어 있다.
즉, 이 메세지를 처리할 윈도우 프로시저를 가지고 있는 윈도우의 핸들을 GetMessage()에서 알게 되고, 핸들을 알게 된다면 그 핸들 클래스의 윈도우 프로시저를 알 수 있다. 따라서 DispatchMessage()에서 해당 윈도우를 처리할 등록된 윈도우 프로시저로 디스패치 해 줄수 있게 된다.
1. Control
일단 컨트롤의 경우는, (컨트롤 이라는 의미를 미리 등록된, 즉, RegisterClass에 등록된 클래스 라고 생각 했다) 자신을 생성한 윈도우(부모 윈도우)에 어떤 이벤트가 발생했음을
PostMessage(hParentWindow, WM_COMMAND, MAKEWORD(ID, NOTIFICATION_CODE), handle);
로 알리는 것일까?
그런데!!!
button클래스를 생각해 보면, 모든 버튼은 이 button 클래스로부터 만들어 지게 되는데, 그렇다면 윈도우즈에 있는 모든 button은 button클래스에 등록된 하나의 윈도우 프로시저에서 처리된다는 것일까?
클래스를 구조체를 보면,
즉, 인스턴스와 프로시저가 같이 등록되게 되어있다. 하지만, 인스턴스마다 다른 윈도우 프로시저를 사용한다는 것은 말이 안된다. 같은 일을 하는 윈도우인데 프로시저가 다름 이상하니깐... 그렇다면?
spy로 button클래스로 생성한 버튼의 윈도우 프로시저와 인스턴스핸들 값을 보면... 어떤 인스턴스이던지 프로시저의 값은 같고, 인스턴스 핸들의 값은 항상 0 이다. ... 쏙았다.... 역시 spy가 짱... -_-;
인스턴스 핸들의 값이 0이라는 것은 어떤 전역적인 인스턴스, 혹은 인스턴스와 상관이 없다는 것일 것이고(이것이 운영체제에서 관리되는어떤 것이라고 생각한다.) 이것은 컨트롤이 받는 메시지 자체가 해당 인스턴스의 메세지 큐와는 관련이 없다는 것을 의미하는 것일것이다. 이렇게 커널에서 관리되는 메세지큐와 프로시저라면, 굳이 Notification을 보내기 위해 PostMessage()api를 사용할 필요는 없을 것이다. 해당 UI 쓰레드의 메세지큐에 집어 넣어 놓으면 펌핑 해 갈테니...(api로 집어 넣었을 수도 있지만, 어차피 운영체제에서 관리 된다면 api가 아닌 좀더 빠른 무언가로 큐에 집어 넣어 놓지 않을까...)
결론을 얘기하면.
1. 버튼이 눌린다.
2. 이 눌림 메세지 자체는 인스턴스와 상관없이 커널단의 메세지 큐에서 관리될 것이다.
3. 해당 메세지 큐에서 메세지가 등록된 윈도우에 해당하는 윈도우 프로시저에 의해 처리된다.
4. 그 컨트롤의 부모 윈도우(hwndParent)가 속한 UI 쓰레드의 메세지 큐에 WM_COMMAND 메세지를 넣어 놓는다.
5. 이제 hwndParent의 UI 쓰레드에서 메세지를 펌핑한다.
6. DispatchMessage()에서 hwndParent가 등록되어 있는 WNDPROC로 이 메세지를 Dispatch한다.
7. 해당 WNDPROC에서 WM_COMMAND를 받는다.
아마도. -_-;
그렇다면, 사용자가 컨트롤을 흉내내는 경우라면,
윈도우 클래스를 등록하고, 이 클래스로 윈도우를 만들고, 클래스에 등록된 윈도우 프로시저에서 2-4의 과정에 해당하는 과정을 해 주면 될 것이다.
만약 버튼의 클릭 되었음을 알린다면, 해당 윈도우 프로시저의 WM_LBUTTONUP 메세지에서
PostMessage(hParentWindow, WM_COMMAND, MAKEWORD(ID, NOTIFICATION_CODE), handle);
해 주면 될 것이다.
2. Menu, Accelerator
Menu는 WM_COMMAND가 전달 되는 것은, 마우스로 메뉴의 아이템을 클릭 했을 때 이고,
Aeccelerator는 WinMain에서 TranslateAccelerator(msg.hwnd, hAccelTable, &msg) 된 정보 이다.
일반적으로 ALT+F(Nmemonic)로 메뉴 자체가 선택되는 것은 이와는 다른 메커니즘인 듯 하다. 나중에 시간 나면 알아봐야지...
Return Value
Each of these functions returns a pointer to the storage location for the copied string or NULL if storage cannot be allocated.
Remarks The _strdup function calls malloc to allocate storage space for a copy of strSource and then copies strSource to the allocated space.
Because _strdup calls malloc to allocate storage space for the copy of strSource, it is good practice always to release this memory by calling the free routine on the pointer returned by the call to _strdup.
별 생각없이 GetSafeHwnd()를 사용하곤 했는데, 소스보니 저리 되있다.
부모 윈도우를 추적하는등의 순환문에서 사용할 경우 따로 NULL인지 판별 해 주지 않아도 되고,
Runtime error를 방지(?) 할 수 있다.
NULL이 아닌것이 확실한 경우 굳이 GetSafeHwnd()를 쓸 이유는 없을듯...
위와 같은 코드를 보시고 class의 static member 변수의 초기화를 안했다고 생각하고 있진 않으신가요?.
표준 C++에서 정의하고 있는 static member 변수 초기화에 대해 간단히 정리했습니다.
A.
먼저 표준 C++에서 정의하고 있는 세가지 storage duration에 대해 간단히 정리해보겠습니다.
static storage duration - dynamic storage duration을 가지지 않으면서 local인 객체들은 모두 static storage duration을 가지게 됩니다. 이 storage에 있는 객체들은 속해 있는 프로그램의 duration만큼 지속됩니다. 또한 local 변수의 선언에 static keyword를 사용하게 되면 역시 static storage duration을 가지게 됩니다. class의 data member에 사용된 static keyword도 마찬가지입니다.
automatic storage duration - auto, register를 명시적으로 선언한 local 객체, 혹은 static, extern등이 명시적으로 선언되지 않은 local 객체들은 automatic storage duration을 가지게 됩니다. 이 storage에 있는 객체들은 자신이 생성된 블록이 끝나기 전까지만 지속됩니다.
dynamic storage duration - 프로그램 수행 중에 new-expressions을 가지고 생성된 객체들은 dynamic storage duration을 가지게 되며 delete-expressions를 사용하여 파괴될 수 있습니다.
위와 같은 용어의 이해를 바탕으로 ISO/IEC-14882의 3.6.2(1)을 참고하면 다음과 같습니다.
Static storage duration을 가지는 객체들을 위한 storage는 어떤 다른 초기화보다 먼저 0으로 초기화된다(zero-initialization).
이러한 초기화와 상수 초기화를 합쳐서 정적(static) 초기화라고 부르고 나머지는 동적(dynamic) 초기화라고 부른다.
static storage duration을 가지는 POD(Plain Old Data) 객체들의 상수 초기화는 항상 어떤 다른 dynamic 초기화보다 먼저 일어나게 된다.
동적 초기화되는 객체들과, 같은 translation unit안의 namespace scope에 정의된 static storage duration을 갖는 객체들은 transation unit안에 선언된 순서에 따라 초기화된다.
// Widget.cpp
int Widget::state_;
이제 state_ 변수는 static storage duration을 가지게 되고 따라서 다른 종류의 초기화에 앞서 0으로 초기화된다는 사실을 알 수 있습니다.
따라서 위의 코드는 초기화를 잊은 코드가 아니라는 사실을 알 수 있습니다.
단지 코드의 작성자는 그 state_ 변수를 0으로 초기화하려고 했었던 것이죠.
참고로 local에 선언된 static 변수는 어떤 다른 초기화 과정에 앞서 0으로 초기화됩니다.
만약 그 변수가 constant-expressions를 가진 POD 타입일 경우 선언되어 있는 블록이 시작되기 전에 초기화되며 이외의 경우 선언된 라인이 처음 실행되는 순간에 초기화됩니다. 이때 객체의 생성자가 exception을 throw하게 되면 다음 실행되는 순간에 다시 초기화를 시도하게 됩니다.
만약 이 객체가 생성되는 순간에 다시 함수에 재진입(re-enter)하게 되면 (recursively) 결과는 정의되어 있지 않습니다(undefined). 아래의 코드를 참고하세요.
int foo(int i) {
static int s = foo(2 * i); // recursive call. undefined
return i + 1;
}
Static storage duration을 가지는 변수들이 하나의 translation unit 안에 정의되어 있다면 선언되어 있는 순서대로 초기화된다는 것을 보장받지만 다른 translation unit 안에 정의되어 있는 static storage duration을 가지는 변수들간의 초기화 순서는 정의되어 있지 않습니다. 따라서 이러한 다른 translation unit의 변수들간의 dependency는 가능하면 피해야 합니다.
하지만 어떠한 경우라도 변수들의 파괴는 정확히 초기화 순서의 반대로 일어납니다.
이 글은 ISO/IEC 14882 문서를 참고하여 만들어졌습니다.
이 페이지의 내용보다 더 자세한 객체의 생성, 삭제에 관련된 다양한 사항들은 위의 문서를 참고하시면 됩니다.