일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 구글 앱 엔진
- 멤버십
- 하이퍼바이저
- 삼성
- 고려대학교
- NarwalFreo
- 빅데이터
- 파이썬
- 나르왈프레오
- Bidirectional Associative Memory
- 인공지능
- 신경회로망
- BAM
- 갤럭시탭S8울트라
- 삼성소프트웨어멤버십
- 신경망
- 가상화
- 삼성전자 소프트웨어멤버십 SSM
- 물걸레로봇청소기추천
- 패턴 인식
- 증강현실
- 패턴인식
- Python
- Friendship
- SSM
- Neural Network
- hopfield network
- 동아리
- 물걸레자동세척로봇청소기
- Google App Engine
- Today
- Total
정보공간_1
[2기 광주 조영진] C++ Template 활용 3번째 본문
안녕하세요
광주멤버십 21-1기 조영진입니다.
저번 포스팅에서는 Template을 활용할 수 있는 한가지 경우에 대해서 알아보았는데요
이번에도 마찬가지로 문제가 발생했을 때 Template을 통해서 해결하는 방법에 대해서
알아보도록 하겠습니다.
그렇다면 일단 먼저 문제가 되는 상황을 살펴보아야겠죠?
1. Object Oriented Programming (OOP)
많은 분들께서 OOP에 대해서 배우실 때 몇가지 개념에 대해서 들은 적이 있으실 겁니다.
캡슐화, 정보 은닉, 추상화, 상속, 다형성 등등 다른말이지만 결론은
OOP에서 Class를 어떻게 설계하는지가 중요하다는 이야기로 통한다고 볼 수 있습니다.
이번 포스팅의 중점은 캡슐화, 정보 은닉과는 큰 관련은 없지만
상속, 다형성, 추상화 측면에서는 많은 관련이 있다고 볼 수 있습니다.
2. 문제가 되는 상황
Print라는 추상 메소드 C++에서는 순수가상함수라고 하는 멤버 함수를 정의하고 있는
interface가 하나 있고 그것을 구현하는 Base라는 Class가 있습니다.
이 Base Class를 상속받는 Derived라는 Class도 역시 Print를 구현하게 되겠죠.
위 상황을 코드로 나타내면 다음과 같습니다.
아직까지는 아무런 문제가 없다고 볼 수 있습니다.
다만 이제 이 Class들을 사용하려고 시도하면 다양한 Error들을 볼 수 있습니다.
먼저 모든 Interface를 출력할 수 있는 하나의 함수를 만들어 보죠.
위와 같은 정말 심플하고도 간결한 함수 입니다.
사용할 때는 더 간결하게 사용할 수 있습니다.
참 쉽죠?
자 이제 위의 코드들을 하나의 소스파일로 만들어서 컴파일을 시도하면!
Compile Error가 발생합니다.. ㅠㅠ
그 이유는 C++에서 Class로 정의된 형식들은 기본 접근 한정자가 Private인데요
이 접근 한정자는 상속을 받는 경우에도 동일하게 적용됩니다.
사실 위 코드는 다음 코드와 동일하다고 볼 수 있습니다.
따라서 main에서 PrintList에 dlist와 blist를 넘겨주면
interface로 변환이 일어나야 하는데 private상속을 받았기 때문에
부모에 접근을 할 수 없어서 Compile Error가 발생하게 되는 것이죠.
이를 해결하기 위해서는 public 상속임을 명시해 주거나
class가 아닌 struct 형식으로 정의해주면 됩니다.
일단 위와 같이 public 상속을 명시해 주고 계속해서 진행하도록 하겠습니다.
이제 public 상속을 명시해주면 Compile Error는 더이상 발생하지 않습니다.
만약 Compile Error가 또 발생한다면 오타나신겁니다..
이제 모든 Compile Error를 해결하고 실행을 딱! 하면
네 잘 됩니다. 우리가 원했던 대로 Derived가 10번 출력되고
Base가 10번 출력됩니다.
자 그렇다면 물론 멤버 함수만 존재하는 Class도 존재하지만
일반적으로 Class는 멤버 함수와 멤버 변수가 함께 들어있죠..
Base는 int 형 멤버 변수가 하나 존재하고
Derived는 double형 멤버 변수가 하나 존재한다고 가정하고
코드를 다음과 같이 수정해 보도록 하겠습니다.
자 이렇게 코드를 변경하고 실행을 딱! 하면!!!!!!!!!!!!
Runtime Error가 발생하죠..ㅠㅠ
이 Runtime Error는 이전 포스팅에서 잠시 언급한
배열은 두가지 경우를 제외하고는 시작 주소와 동일하게 사용된다는 내용 때문입니다.
위 코드의 PrintList에 적힌 list[i]는 사실 *(list + i) 과 동일한 코드입니다.
따라서 list[0]은 *(list + 0) 이고 list[1]은 *(list + 1)인 것이죠.
결국 C++에서 배열에 대한 접근은 배열의 시작 주소에서 시작하여
offset(여기서는 배열의 요소의 크기만큼)을 가지고 주소값을 이동하면서
그 주소에 들어있는 메모리를 읽어오도록 동작하도록 구현되어있고
우리가 PrintList에 알려준 배열의 요소의 크기는 4Byte이지만
실제 Base Class는 8 Byte이고 Derived Class는 16Byte이기 때문에
정상적인 Base 객체와 Derived객체를 가져오지 못해서 생기는 문제입니다.
자 이제 이 문제를 해결하기 위한 방법에 대해서 알아보도록 하겠습니다.
3. 해결하는 방법
C++에서 이 문제를 해결하는 방법은 여러가지가 있겠지만 가장 간단한 방법은 역시
template을 활용하는 것 입니다.
위와 같이 하시면 좀 전에 발생했던 문제는 해결이 되게 됩니다.
하지만 저번 포스팅을 보신 분들은 template을 사용하게 되면
배열의 크기를 따로 넘겨줄 필요가 없다는걸 기억하실 겁니다.
이 전 포스팅에서는 template specialization을 통해서 정적배열과 동적배열을 구분했었는데요
이번에는 STL에서 제공하고 있는 template을 사용해서 해결해 보도록 하겠습니다.
일단 사용법은 위와 같습니다.
저번 포스팅에서 얘기했던 것처럼 정적배열의 경우에는 _msize를 사용할 수 없고
동적배열의 경우에는 sizeof를 사용하면 원하는 값이 나오지 않기 때문에
PrintList의 인자로 전달된 대상이 정적배열인지 동적배열인지에 따라
서로 다른 코드가 수행되도록 해야 합니다.
코드만 보시면 T가 array 라면 sizeof를 통해서 length를 계산하고
T가 pointer라면 _msize를 통해서 length를 계산하도록 되어 있는 코드입니다.
자 이제 여기서 is_array 와 is_pointer 그리고 true_type에 대해서만 알면
위 코드를 이해하는데 무리가 없으리라고 생각됩니다.
먼저 true_type에 대해서 알아보도록 하겠습니다.
Visual Studio에서 true_type의 위치에서 마우스 오른쪽을 누른 다음
정의로 이동, 또는 F12키를 누르면 위와 같은 코드를 보실 수 있습니다.
잘 보시면 integral_constant를 bool형식에 true값을 갖도록 하는 타입을 true_type이라고
typedef 해 놓은 것을 확인할 수 있습니다.
integral_constant는 바로 위쪽에 다음과 같이 정의되어 있습니다.
지금 저는 Visual Studio 2012 버전에서 모든 코드를 확인하고 있는데 이는
Library를 구현한 Vender에 따라 달라질 수 있습니다.
GCC나 Visual Studio 다른 버전 등에서는 위와 다른 코드를 보실 수도 있습니다.
위 integral_constant를 살펴보시면 value라고 하는 static const 값을 가지고 있으며
integral_constant를 생성한 _Ty 즉 Type을 value_type으로 재정의 하고 있으며
생성된 integral_constant를 type으로 재정의하고 있고
함수호출 연산자를 재정의하여 해당 value_type형식으로 저장되어 있는 value값을 반환하도록
정의되어 있습니다.
다시 위의 true_type을 보시면
integral_constant<bool, true>라고 되어 있는데 이것은 integral_constant를 사용해서
bool 형식 중에서 true값을 가지는 데이터를 하나의 타입으로 만들어 놓은 것입니다.
마찬가지로 false_type은 bool 형식 중에서 false값을 가지는 데이터를 별도의 타입으로 만들어놓은 것이죠.
이제 실제 is_array를 살펴보도록 하겠습니다.
is_array는 다음과 같이 정의되어 있는데요 template 인자로 넘어온 _Ty가
[] 또는 [_Nx] 형식인 경우에는 true_type을 상속받도록 되어있고
아닌 경우에는 false_type을 상속받도록 되어 있습니다.
즉 하나의 T를 통해서 is_array 객체를 생성하게 되면 true_type의 자식 또는
false_type의 자식이 되게 됩니다.
그리고 위의 integral_constant에서 각각의 Type을 구분하기 위해 value라는 정적 상수를 사용했으니
해당 값을 비교하여 같은 Type인지 확인할 수 있습니다.
그 부분이 위에서 살펴본 if(is_array<T>() == true_type()) 부분입니다.
위 코드는 연산자 오버로딩을 통해서 재정의 되어있는 () 연산자를 사용했지만
if(is_array<T>::value == true_type::value) 처럼 해도 동일하게 동작합니다.
결국 같은 코드니까요.
is_pointer의 경우에는 방식은 비슷하지만 조금 복잡하게 구현되어 있습니다.
배열의 경우에는 어떤 대상에 대한 배열인지 명확하게 정의되어 있기 때문에 해당 Type이
배열인지 아닌지만 확인하면 되지만 포인터의 경우에는
이 포인터가 가리키고 있는 대상이 무엇인지 알 수 없기 때문에
이를 알아내는 과정이 포함되어 있는 것 입니다.
특히 멤버 변수에 대한 포인터나 멤버 함수에 대한 포인터는 해당 Class에 따라서
Type자체가 달라지게 됩니다.
위와 같이 정의되어 있는 Class들과 변수와 함수를 살펴보도록 하겠습니다.
일단 일반 변수와 함수에 대한 포인터는 쉽게 접근할 수 있는데요
위와 같은 코드를 통해서 일반 변수와 함수에 대한 포인터를 사용할 수 있습니다.
그렇다면 같은 방법으로 멤버 함수와 멤버 변수에 대한 접근을 해볼까요?
멤버 변수의 경우에는 비슷한 방식으로 접근이 가능합니다.
하지만 멤버 함수의 경우에는 조금 다른데요.
위 코드를 컴파일 하려고 하면 Compile Error가 발생하게 됩니다.
그 이유는 지극히 당연한데 많은 분들이 아시다시피 멤버 변수는 각 객체마다
메모리 공간을 차지하게 됩니다.
따라서 s2객체를 생성하게 되면 그 객체가 가지고 있는 member_variable은 별도의 메모리 공간에
위치하게 되기 때문에 각 객체별로 주소값이 다르게 됩니다.
따라서 그곳을 가리키는 포인터를 특정하게 지정할 수 있는 것이죠.
하지만 멤버 함수는 하나의 Class에서 정의되면 객체마다 새로 생성하는 것이 아닙니다.
쉽게 생각하면 멤버 함수는 static 멤버 변수와 동일한 형식이라고 생각하시면 됩니다.
객체마다 생성되는 것이 아니라 해당 Class에 종속되는 정보인 것이죠.
그렇다면 멤버 함수에 대한 포인터는 어떤식으로 선언해야 되는지 감이 오실 겁니다.
객체를 통해서 선언하는 것이 아니라 Class를 통해서 선언을 해야 합니다.
위와 같이 선언을 하면 되고 Sample에 대한 member_function과 Sample2에 대한 member_function은
서로 다른 타입에 대한 함수이기 때문에 상호 호환이 되지 않습니다.
그리고 일반 함수 포인터는 그냥 함수처럼 사용하면 되지만 멤버 함수 포인터는
멤버 함수를 호출하기 위해서는 객체가 필요하듯이 멤버 함수 포인터를 호출할 때에도 객체가 필요한데
다음과 같이 하면 됩니다.
여기서 .* 이라는 연산자는 멤버 함수 포인터에 접근하기 위한 연산자이고
()를 쳐준 이유는 연산자 우선순위에 의해서 함수 호출 연산자인 ()연산자가 우선순위가 높기 때문에
멤버 함수 포인터에 먼저 접근하도록 하기 위해 (s1.*member_function_ptr)처럼 처리를 해 준 것입니다.
이제 다시 PrintList 함수로 돌아가서
위 코드를 수행해 보면
위와 같이 의도한 대로 정상적으로 동작하는 것을 확인할 수 있습니다.
이번 시간에도 template을 활용하는 상황에 대해서 알아보았는데요
계속 template만 다루는 것 같아서 다음 시간에는
C++에 template이 존재한다면 Java나 C#에는 Generic이라는 내용이 있는데
이들의 차이점과 장단점에 대해서 알아보는 시간을 갖도록 하겠습니다.
감사합니다^^
'IT 놀이터 > Elite Member Tech & Talk' 카테고리의 다른 글
[2기 대전 김호원] Concerto MCU 3.클럭 설정과 GPIO (1) | 2012.10.16 |
---|---|
[2기 광주 박정태]Arduino 의 모든것#2 (0) | 2012.10.15 |
[2기 수원 이동열] VMWare의 Virtualization (0) | 2012.10.15 |
[2기 강남 김주영] 쉽고 즐거운 유전자 알고리즘 #2 (TSP의 해결) (3) | 2012.10.08 |
[2기 수원 이동욱] 소리의 이해 (2/2) (0) | 2012.09.26 |