말은 거창하지만 다른건 없다. 해당 값을 입력 받을 경우 다른 변수에 값을 추가 하거나 기타 다른 이벤트를 처리 할때 해당 값에 의존적인 클래스에서 처리 할 수 있다는 부분이다. m_Val = 100 이렇게 입력 받은 후에 다른 이벤트 처리를 의존 프로퍼티 클래스를 상속받은 클래스에서 처리가 가능하게 만들어 본겁니다. ^^.. 사용은 해봐야 알겠지만. 괜찮은 확장이 가능해 질거 같네요..
--라이브러리 클래스--
class CGsiDependencyProperty { public: CGsiDependencyProperty() {} virtual ~CGsiDependencyProperty() {}
//값이 바뀌게 되면 호출할 수 있도록 바인딩 되는 함수 virtual void PropertyChanged() { int iii = 0; }; };
가끔 요즘 나오는 기술들을 보다 보면 데이터 바인딩에 대한 내용이 잘 구현되어있는거 같다. 물론 이해는 100% 되지 않지만 말이다.
그래서 C++에서 나름대로 사용하면 이런 형태는 어떨까 해서 하나 만들어 봤다. ^^ 조금더 다듬어서 일부 기능은 이것을 사용해도 될거 같다.
사용되는 구조는 데이터를 가지고 있는 객체를 CContain 클래스 처름 구헌한다고 가정을 해보면 CDataBind 클래스와 연결후에 CDataBind의 객체만 SetValue 하면 된다. 이때 CContain 인스턴서의 PropertyChanged()가 자동으로호출 될 수도 있다.
이것과 다르게 함수포인터를 연동하는 부분도 추가를 해봤다. 클래스 내부의 함수가 함수 포인터로 하기에는 조금 몇가지 제약사항이 있기 때문에 전역 함수를 포인터와 연동하고 값이 바뀌게 되면 해당 함수가 호출 되는 구조를 취하게 된다.
혹.. 필요하신분 있으시면 요청시 설명해드릴게요 ^^ 혹.. 좋은 의견 있음 코멘트 부탁 해요. ^^
vector를 사용해서 객체를 저장할때 포인터 형으로 하게 되면 메모리 누수를 막기 위해서 객체를 항상 Delete 해줘야 한다. 솔직히 매번 Add, Delete, DeleteAll 등을 해줘야 하는 불편함이 전(??) 있었다. ^^ 그래서 템플릿으로 하나 만들었다. for-each 이건.. 왜 안되는지 ^^.. 암튼.. 현재걸로 조금 우선 사용해보자.
Dialog Form (MFC) 를 개발하다 보면 CView, CScroll, CHtmlView 등과 같은 뷰의 클래스를 추가 하고 싶을때가 있다.
하지만 동적으로 생성하는 방법을 사용해서 CMyView* pMyView = new CMyView(); pMyView.Create(...); pMyView.ShowWindow(SW_SHOW);
이런 방법을 사용하게 되면 생성후에 화면에 제대로 나오는걸 확인할 수 있다. 하지만 종료할때 보면 디버그 에러가 발생하게 된다.
자세한 이유는 모르겠다. 하지만 분명한건 아래와 같은 RUNTIME_CLASS() 를 사용해서 생성해야 하는걸 확인하였다. 아래와 같이 코드를 작성해서 추가를 해보면 이상없이 동작하는 것을 확인할 수 있다.
/// BOOL CDlgTestViewDlg::OnInitDialog() { ... CCreateContext pContext; /** * Note:CDialig derived pointer is converted to * CWnd pointer (a common base class for CDialog and CFrameWnd). * Thus casting it back to CFrameWnd is also easy. */ CWnd* pFrameWnd = this;
pContext.m_pCurrentDoc = new CDocument; pContext.m_pNewViewClass = RUNTIME_CLASS(CTestView); CTestView *pView = (CTestView *) ((CFrameWnd*)pFrameWnd)->CreateView(&pContext); ASSERT(pView); pView->ShowWindow(SW_NORMAL); /** * After a view is created, resize that to * have the same size as the dialog. */ CRect rectWindow; rectWindow = CRect(10, 10, 100, 100); /** * Leave a little space for border and title... */ pView->MoveWindow(rectWindow);
dynamic_cast(e)는 부모 클래스와 자식 클래스의 관계에 있는 포인터 형 사이의 변환 또는 레퍼런스 형 사이의 변환을 수행하는데,
- 같은 형 사이의 변환 - 널 포인터의 변환 - 자식 클래스로부터 부모 클래스로의 변환 과 같은 '뻔한' 경우가 아니라면 e는 다형적 형(polymorphic type; 가상 함수가 포함된 클래스 형)의 좌변값이나 포인터여야 하며, 컴파일시에 변환이 이루어지는 다른 종류의 캐스트 연산과는 달리 실행시에 동적 형(dynamic type)에 근거한 변환이 시도되고, 변환의 성공 여부를 검사하는 의미도 함께 가지고 있습니다.
포인터의 경우 변환이 실패하면 결과값은 널 포인터가 되는데, 이를 if 등의 조건 검사에 활용할 수 있습니다.
void test(animal* a) { if (dog* d = dynamic_cast(a)) d->bark(); else if (cat* c = dynamic_cast(a)) c->mew(); }
레퍼런스의 경우 변환이 실패하면 헤더에 정의되어 있는 std::bad_cast 예외가 발생합니다. 즉 이는 주어진 변환이 성공할 것을 알고 있을 때 주로 사용합니다.
void test(animal& a) { dog& d = dynamic_cast(a); d.bark(); }
dog d; test(d); // 성공 cat c; test(c); // 실패 - std::bad_cast 예외 발생
dynamic_cast와 비슷한 성질을 가지고 있으면서 변환 대신 형 검사만 하는 typeid 연산자도 있는데, 피연산자는 식이나 형이 되고, 연산의 결과값은 헤더에 정의되어 있는 std::type_info 형의 좌변값입니다.
void test_equal(animal& x, animal& y) { if (typeid(x) == typeid(y)) { /* 같은 종류 */ } else { /* 다른 종류 */ } }
void test_dog(animal& x) { if (typeid(x) == typeid(dog)) { /* x is a dog */ } }
dynamic_cast와 typeid는 C++에서 제공하는 실행시의 형 정보(RTTI; run-time type information)의 일환인데, 이를 남발하면 클래스 체계를 확장하고 관리하기가 어려워지므로 꼭 필요한 경우에만 사용하고 되도록 가상 함수를 통한 다형성을 이용하는 것이 바람직합니다.
-------------- 2. static_cast --------------
static_cast(e)는 가장 일반적인 형태의 캐스트 연산으로, 어떤 임시 변수 t를 T t(e);와 같이 선언하고 초기화하여 그 임시 변수 t의 값을 사용하는 것과 같은 암시적인 변환을 비롯하여, 산술형(char, int, double 등) 및 열거형(enum) 사이의 변환, 부모 클래스와 자식 클래스의 관계가 관련된 변환, void 형으로의 변환 등을 수행할 수 있습니다. 다만 부모 클래스와 자식 클래스 사이의 변환은 주어진 식의 동적 자료형(dynamic type)이 아닌 정적 자료형(static type)에 전적으로 의존합니다.
inline int integer_quotient(double a, double b) { return static_cast(a / b); }
animal* a = new dog; dog* d = static_cast(a); // 올바른 캐스트 연산
animal* a = new cat; dog* d = static_cast(a); // 잘못된 캐스트 연산
animal* a = new animal; dog* d = static_cast(a); // 잘못된 캐스트 연산
reinterpret_cast(e)는 서로 다른 형의 포인터 사이의 변환이나, 정수와 포인터 사이의 변환 등 서로 관계가 없는 형 사이의 변환을, 구현체가 정의하는 방법에 따라 수행합니다. 정수와 포인터 사이의 변환의 결과값은 주로 e를 표현하는 비트열을 그대로 정수 및 포인터로 해석한 값이 됩니다.
reinterpret_cast는 그 변환 방법이 대부분 구현체가 정의하도록 맡겨져 있어서 이식성을 떨어뜨리며, 요구된 변환이 올바른 변환인지의 여부를 검사하지 않으므로 신중하게 사용해야 합니다.
위 네 가지 중에서 dynamic_cast를 제외한 셋은 C에서 (type)expression 의 형태로 사용할 수 있었던 것인데, C++에서도 "C 스타일 캐스트 연산" 이라고 불리며 남아 있기는 합니다만, 새로 작성하는 C++ 코드에서는 C++ 스타일의 캐스트 연산을 사용하는 것이 좋습니다. 이는 어떤 종류의 변환을 프로그래머가 의도하는지 명확하게 나타내 주며, 위험할 수 있는 캐스트 연산이 코드에서 좀 더 두드러져 보이도록 하고 찾기도 쉽게 만들어주기 때문입니다.
위의 내용은 캐스트 연산에 대한 일반적인 설명인데, 좀 더 구체적인 상황에서의 적용 예를 보고 연습을 해 보시려면 GotW #17을 참조해 보시기 바랍니다.
함수 포인터 타입도 일종의 고유한 타입이다. 따라서 원형이 다른 함수 포인터끼리는 곧바로 대입할 수 없으며 함수의 인수로도 넘길 수 없다. 정수형 포인터 변수(int *)에 실수형 포인터 변수(double *)의 값을 대입할 수 없듯이 말이다. 다음 코드를 보자.
int (*pf1)(char *);
void (*pf2)(double);
pf1=pf2;// 타입이 다르므로 에러
pf1은 문자형 포인터를 인수로 취하고 정수형을 리턴하는 함수를 가리키는 함수 포인터이며 pf2는 실수를 인수로 취하고 리턴값이 없는 함수를 가리키는 함수 포인터이다. 두 변수가 가리킬 수 있는 함수의 원형이 다르기 때문에 pf2가 가리키는 번지를 pf1에 곧바로 대입할 수 없다. 만약 이것이 가능하다면 pf1로 함수를 호출할 때 컴파일러는 char *형의 인수를 찾지만 pf1이 가리키는 함수는 double형의 인수를 받아들이므로 불일치가 발생하며 함수가 제대로 호출되지 않을 것이다.
함수 포인터가 가리킬 수 있는 원형과 같지 않은 함수의 번지를 대입하는 것도 똑같은 이유로 에러로 처리된다. 다음 코드에서 pf3은 문자형 포인터와 실수를 인수로 취하고 리턴값이 없는 함수를 가리키도록 선언했는데 func 함수는 pf3의 원형과 다르므로 pf3에 func 함수의 번지를 대입할 수 없다.
int func(int a);
void (*pf3)(char *, double);
pf3=func;// 에러
그러나 타입이 다른 함수 포인터끼리라도 강제로 대입할 수는 있는데 이것이 일단은 가능해야 한다. void 포인터에 저장된 함수의 번지를 대입받는다거나 자료 구조 설계시에 미리 알 수 없는 함수에 대한 포인터를 다루고자 할 때이다. 이럴 때 사용하는 것이 바로 캐스트 연산자이며 여러 가지 이유로 강제 캐스팅의 필요성은 누구나 인정하고 있다. int *pi와 double *pd가 있을 때 pi=pd 대입은 금지되지만 pi=(int *)pd는 가능한 것처럼 함수 포인터도 타입에 맞게 캐스팅하면 강제로 대입할 수 있다.
데이터 포인터에서와 마찬가지로 함수 포인터에도 캐스트 연산자를 쓸 수 있는데 문제는 함수 포인터의 캐스트 연산자가 모양이 생소해서 조금 어렵다는 것이다. 함수 포인터는 타입 자체가 길기 때문에 캐스트 연산자의 모양도 상당히 복잡해 보인다. 다음 코드는 pf2를 pf1에 강제로 대입하기 위해 캐스트 연산자를 사용한 것이다.
int (*pf1)(char *);
void (*pf2)(double);
pf1=(int (*)(char *))pf2;
이 식에서 (int (*)(char *))가 캐스트 연산자이다. 함수 포인터형의 캐스트 연산자를 만드는 방법은 함수 포인터 선언식에서 변수명을 빼고 전체를 괄호로 한 번 더 싸주면 된다. 일종의 공식이므로 외워 두거나 아니면 필요할 때마다 이 공식을 찾아보기 바란다.
pf1=(int (*)(char *))pf2 대입문은 pf2가 가리키는 번지를 문자형 포인터를 인수로 취하고 정수를 리턴하는 함수 포인터 타입으로 잠시 바꾼 후 pf1에 대입한다. pf3에 func 함수의 번지를 강제로 대입할 때도 마찬가지로 캐스트 연산자를 사용할 수 있다. 물론 이렇게 강제로 대입했을 때의 부작용에 대해서는 스스로 책임져야 한다.
pf3=(void (*)(char *,double))func;
캐스트 연산자가 길어 보여서 그렇지 원리를 알고 나면 별로 어렵지 않다. 다음은 조금 이론적인 얘기가 되겠지만 함수 포인터의 배열이나 포인터를 선언하는 형식에 대해 알아보자. T형에 대해 T형 배열과 T형 포인터를 항상 선언할 수 있으므로 함수 포인터에 대해서도 배열과 포인터를 선언할 수 있다. func 타입의 함수를 가리킬 수 있는 함수 포인터를 요소로 가지는 크기 5의 arpf 배열은 다음과 같이 선언한다.
int (*arpf[5])(int);
함수 포인터 배열을 선언할 때는 변수명 다음에 첨자 크기를 밝혀 주면 된다. 잘못 생각하면 int (*arpf)(int)[5];가 맞을 것 같기도 한데 첨자 크기는 반드시 변수명 다음에 바로 써야 한다. 이 선언에 의해 int (*)(int)형의 함수의 번지를 가리킬 수 있는 함수 포인터 arpf[0] ~ arpf[4]까지 4개의 변수가 생성되며 각 변수는 int func(int)와 같은 원형의 함수를 가리키는 번지를 가질 수 있다. 동일한 타입의 변수들을 배열에 모아 두면 루프를 돌면서 함수들을 순서대로 호출한다거나 하는 처리도 가능해진다.
다음은 함수 포인터의 포인터를 선언해 보자. func 타입의 함수를 가리키는 함수 포인터를 가리키는 포인터 ppf는 다음과 같이 선언하는데 * 구두점만 하나 더 적으면 된다.
int (**ppf)(int);
이렇게 선언된 ppf는 int (*)(int) 타입으로 선언된 함수 포인터 변수나 함수 포인터 배열을 가리킬 수 있는 이차 함수 포인터 변수이다. ppf=&pf 또는 ppf=arpf 식으로 함수 포인터 변수의 번지를 대입받을 수 있으며 ppf로부터 함수를 호출할 때는 (**ppf)(2) 형식을 사용한다. 함수 포인터 배열 arpf와 이중 함수 포인터 ppf가 메모리에 구현된 모양은 다음과 같다.
함수 포인터의 타입은 함수가 취하는 인수들의 타입과 리턴값까지 정확하게 밝혀야 하기 때문에 타입의 형식이 너무 길어서 쓰기에 번거롭다. 또한 함수 포인터로부터 파생된 타입을 만드는 것도 헷갈리고 생소한 면이 있다. 그래서 함수 포인터 타입을 자주 사용하거나 자신이 없다면 직접 타입을 기술하는 것보다 typedef로 함수 포인터 타입을 따로 정의한 후 사용하는 것이 편리하다. int func(int)형의 함수를 가리키는 타입은 다음과 같이 정의한다.
typedef int (*PFTYPE)(int);
PFTYPE pf;
함수 포인터를 선언하는 문장에서 변수명을 원하는 타입 이름으로 바꾸고 앞에 typedef만 붙이면 된다. 이후 컴파일러는 PFTYPE이라는 명칭을 int (*)(int) 타입으로 인식하므로 PFTYPE으로 함수 포인터 변수를 쉽게 선언할 수 있으며 캐스트 연산자로도 사용할 수 있다. 또한 함수 포인터로부터 배열이나 포인터같은 파생 변수를 선언하는 것도 훨씬 더 간편하다.
PFTYPE arpf[5];
PFTYPE *ppf;
마치 int형으로부터 배열이나 포인터를 선언하듯이 PFTYPE을 사용할 수 있으므로 직관적이고 읽기에도 좋다.