검색결과 리스트
Programming/C++에 해당되는 글 3건
- 2014.10.26 dynamic_cast 연산자
- 2014.08.25 .h 컴파일 : #ifndef vs. #pragma once
- 2014.08.19 정적 바인딩(Static binding) vs. 동적 바인딩(Dynamic binding)
나는 여태까지 C++ 은 정적 컴파일이다보니
동적으로 뭔가를 한다는 게 불가능할 줄 알았는데,
그냥 내가 무식했나보다...ㅎㅎㅎ..
C++ 에서 4 가지 캐스팅 연산자 (dynamic_cast, static_cast, const_cast, reinterpret_cast) 가운데
dynamic_cast 에 대해 알아보고자 한다.
dynamic_cast 는 상속 관계 안에서 포인터나 참조자의 타입을
기본 클래스 → 파생 클래스 로의 다운캐스팅과
다중 상속에서 기본 클래스 간의 안전한 타입 캐스팅에 사용된다.
(안전한 타입 캐스팅 : 런타임에 타입 검사를 하겠다는 의미)
const_cast 와 같이 용도가 명확하기 때문에 다른 용도로는 사용하지 못한다.
+추가로
객체가 위치한 메모리의 시작부분을 찾는 데도 사용된다고 한다.
아래와 같이 p 에 시작주소가 담겨진다.
1 | void * p = dynamic_cast < void *> (ObjectPointer); |
* dynamic_cast 의 제약사항
1) 상속 관계 안에서만 사용할 수 있다.
2) 하나 이상의 가상 함수를 가져야 한다.
3) 컴파일러의 RTTI (Runtime Type Information) 설정이 켜져있어야 한다.
[예제1]
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 32 33 34 35 36 37 38 39 | class Base { public : virtual void print() { cout << "This is Base" << endl; } }; class Derived : public Base { public : void print() { cout << "This is Derived" << endl; } }; void main() { Base* pBase1 = new Base; Base* pDerived1 = new Derived; Derived* pDerived2 = new Derived; Derived* pDerived = nullptr; // 컴파일 실패 : 부모는 자식이 될 수 없음 pDerived = pBase; // 컴파일 성공 : but 부모는 자식이 될 수 없으므로 널 포인터를 리턴한다! pDerived = dynamic_cast <Derived*> (pBase); if ( pDerived == nullptr ) cout << "failed to casting pBase->pDerived" << endl; // 컴파일 실패 : pDerived1 의 타입이 Base* 이므로, 부모는 자식이 될 수 없음 pDerived = pDerived1 // 컴파일 성공 : 런타임에 타입 변환이 성공하여 Derived 타입의 포인터를 리턴한다! pDerived = dynamic_cast <Derived*> (pDerived1); if ( pDerived ) pDerived->print(); // 컴파일 성공 : 이런 경우에는 캐스팅이 필요없다. pDervied = pDerived2; } |
위 예제에서의 키포인트는
'포인터가 실제로 가르키는 대상이 캐스팅이 될 수 있는 녀석인가' 이다.
dynamic_cast 는 캐스팅에 실패할 경우,
대상이 pointer 면 nullptr 를 반환하고
대상이 참조자이면 bad_cast 예외를 던진다.
이 점이 위에서 언급한 안전한 타입 캐스팅의 의미이다.
캐스팅의 가능 여부를 런타임에 검사하여 처리할 수있다.
다음은 다중 상속일 때, 기본 클래스 간의 타입 캐스팅(Cross-Casting)을 다룬다.
[예제2]
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 | class BaseOne { public : virtual void print() { cout << "This is BaseOne" << endl; } }; class BaseTwo { public : virtual void print() { cout << "This is BaseTwo" << endl; } }; class Derived : public BaseOne, public BaseTwo { public : void print() { cout << "This is Derived" << endl; } }; void main() { BaseOne* pBaseOne = nullptr; BaseTwo* pBaseTwo = new Derived; // 컴파일 실패 : BaseOne 과 BaseTwo 타입은 호환 불가 pBaseOne = pBaseTwo; // 컴파일 성공 : 기본 클래스 간의 타입 캐스팅 가능 pBaseOne = dynamic_cast <BaseOne*> (pBaseTwo); if ( pBaseOne ) pBaseOne->print(); } |
[예제2] 를 정리하자면 아래의 그림과 같다.
크로스 캐스팅의 한 사용 예로는
각 기본 클래스 포인터(or 참조자) 타입의 컨테이너 혹은 배열을 운용하면서
서로 간의 요소를 교환할 때 사용할 수 있다.
여기까지 dynamic_cast 의 용도를 알아보았다.
( [예제1] : 다운 캐스팅, [예제2] : 크로스 캐스팅 )
참고로 다중 상속은 권장하지 않는 분위기(?)라서 설계를 재검토 하도록 하자.
※ 포스팅 참고 자료:
http://msdn.microsoft.com/ko-kr/library/cby9kycs.aspx
포스팅의 엄청난 도움을 주신 (+게으른 저를 포스팅하게 만들어 주셨습니다)
http://prostars.net/55 님께 감사의 인사를 덧붙입니다. (__)
.h 컴파일 : #ifndef vs. #pragma once (0) | 2014.08.25 |
---|---|
정적 바인딩(Static binding) vs. 동적 바인딩(Dynamic binding) (0) | 2014.08.19 |
중복 컴파일 방지를 위해 자주 쓰이는 전처리기 지시자들에 대해 비교해 보고자 한다.
1) #ifndef
먼저 다음과 같은 코드가 있다고 하자.
First.h
1 2 3 4 5 6 7 | #ifndef _FIRST #define _FIRST class First { }; #endif |
Second.h
1 2 3 4 5 6 7 8 9 | #ifndef _SECOND #define _SECOND #include "First.h" class Second { }; #endif |
Main.cpp
1 2 3 4 5 6 | #include "First.h" #include "Second.h" void Main() { } |
Main.cpp 를 컴파일 해보면 #include 의 작동방식에 따라
그 파일의 코드가 그대로 복사될 것이다.
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 | #ifndef _FIRST #define _FIRST class First { }; #endif //////////////////////////////// #ifndef _SECOND #define _SECOND #include "First.h" class Second { }; #endif //////////////////////////////// void Main() { } |
위와 같이 되고 Second 에서 또 한번 #include "First.h" 를 읽게 될 것이다.
그 결과로 ,
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 | #ifndef _FIRST #define _FIRST class First { }; #endif //////////////////////////////// #ifndef _SECOND #define _SECOND #ifndef _FIRST #define _FIRST class First { }; #endif class Second { }; #endif //////////////////////////////// void Main() { } |
위의 코드와 같이 Main.cpp 에는 First.h 이 2번 복사되는 결과를 볼 수 있다.
결론을 내리자면, #ifndef 의 방식으로 중복 컴파일을 피하는 것은
#include 명령어를 사용한 횟수 만큼 전처리기가 그 파일을 복사해오고,
복사해 온 코드 상단에 작성된 #ifndef 구문을 읽고 컴파일 여부를 확인 한다는 것이다.
이렇게 결론을 지으면
'그런데도 왜 이 방식을 써서 중복 컴파일 방지를 하냐 ?!'
라고 할 수 있다. 물론 장점도 존재하고 포스팅 마무리할 때쯤 언급하겠다.
2) #pragma once
그런데 나는 - pragma - 라는 단어가 대체 무슨 뜻인지 궁금해서 찾아보았다.
짜증. 괜히 맛있는 치킨만 먹고 싶어졌다.
그냥 컴파일러 지시자? 정도로만 이해해야겠다.
구글에 검색해도 pragma 가 뭐 하는 녀석인지만 나와있지,
정작 pragma 라는 단어 자체의 뜻에 대해서는 잘 안 나오는 듯_-_
어쨌든 잡소리였고 다시 중복 컴파일 방지를 위한 #pragma once 명령어로 돌아가자.
이 지시자가 의미하는 바는 '1번만 컴파일 하겠다' 라는 뜻으로 알고 있다.
msdn 에 따르면 (http://msdn.microsoft.com/en-us/library/4141z1cx(v=vs.80).aspx)
Pragma 지시자의 토큰 값으로 들어갈 수 있는 once 키워드 :
Specifies that the file will be included (opened) only once by the compiler when compiling a source code file.
Remark : This can reduce build times as the compiler will not open and read the file after the first #include of the module.
아까 말한 것과 다를 바 없고, 이게 전부다.
#ifndef 과 다르게 include 로 인해 여러 번 파일을 열어서 읽는 것이 아니라서 빌드 타임을 감소시킬 수 있다는 점.
3) 정리
#ifndef :
- 앞서 보았듯이 헤더 파일을 여러 번 include 하게 되면 define 여부를 계속 체크해야 되기 때문에
컴파일 단계 중에서 파일 해석 단계의 속도가 #pragma once 에 비해서 다소 느리다고 할 수 있다.
하지만 장점을 꼽자면 전처리기 지시자(Preprocessor directive)라서 모든 컴파일러에서 동작한다는 점이다.
#pragma once :
- 1번만 컴파일 하고 그 뒤로부터는 동일한 파일의 경우, 읽기조차 하지 않는다. 그래서 파일 해석 단계의 속도가
#ifndef 보다는 빠르다. 하지만 아까 사전적 의미를 찾아봤을 때, 자세히 보면 'A compiler directive' 라고 적혀있다.
컴파일러 지시자로 특정 컴파일러에서만 동작하는 지시자이며 Visual C++ 5.0 이상에서만 동작한다고 한다.
얼핏 속도면에서 보면 #pragma once 가 좋아보일 수 있으나
거대 프로젝트가 아닌 이상 속도에서 큰 차이를 볼 수 있을까? 하는 의문이 든다.
그리고 안정성(호환성)과 범용성을 고려한다면 #ifndef 방식을 택해야 할 것이다.
4) Reference
http://neodreamer-dev.tistory.com/310
http://msdn.microsoft.com/en-us/library/t22e924w(v=vs.80).aspx
dynamic_cast 연산자 (0) | 2014.10.26 |
---|---|
정적 바인딩(Static binding) vs. 동적 바인딩(Dynamic binding) (0) | 2014.08.19 |
* Binding
- 프로그램 구성 요소의 성격을 결정해주는 것
ex ) 변수의 데이터 타입이 무엇인지 정해지는 것
종류 | 정적 바인딩(Static binding) |
동적 바인딩(Dynamic binding) |
정의 | 컴파일 시간에 성격이 결정되는 것 |
실행 시간(runtime)에 성격이 결정되는 것 |
예시 | C언어 컴파일 시간에 변수의 데이터 타입이 결정 | Python(Interpreter 언어) 런타임에 값에 따라 변수의 데이터 타입이 결정 |
장단점 | 컴파일 시간에 많은 정보가 결정되므로 실행 효율↑ | 런타임에 자유롭게 성격이 바뀌므로 적응성↑ |
* 함수의 바인딩
- 함수를 만들어 컴파일을 하면 각각의 코드가 메모리 어딘가에 저장된다.
그리고 함수를 호출하는 부분에는 그 함수가 저장된 메모리 번지수(주소값)이 저장된다.
프로그램 실행 → 함수 호출 → 함수가 저장된 주소로 점프 → 함수 실행 → 원래 위치
위 과정에서 함수를 호출하는 부분에 함수가 위치한 메모리 번지로 연결시켜 주는 것을 바인딩(Binding) 이라고 한다.
- 함수를 바인딩하는 2가지 방법
(1) 정적 바인딩 (일반 함수)
컴파일 시간에 호출될 함수로 점프할 주소가 결정되어 바인딩 되는 것.
(2) 동적 바인딩 (가상 함수)
실행 파일을 만들 때 바인딩 되지 않고 보류 상태 둔다.
점프할 메모리 번지를 저장하기 위한 메모리 공간(4 byte)을 가지고 있다가 런타임에 결정.
=> 단점 : 타입 체킹으로 인한 수행 속도 저하 / 메모리 공간 낭비
=> 가급적 정적 바인딩 사용
?? 2 가지의 단점이 있음에도 불구하고 동적 바인딩을 하는 이유 ??
- 어떤 포인터에 의해 접근되었는 지에 상관없이 참조된 인스턴스의 실제 클래스형에 따라 재정의된 함수 호출이 가능!
이 포스팅의 원본 출처이자 C++ 에서
Template, 정적 바인딩 vs. Virtual Function, 동적 바인딩 으로 실험한 결과를 볼 수 있는 블로그 :
위 출처에서 정적 바인딩이 항상 동적 바인딩 보다 빠르다고 할 수 없다는 결과를 볼 수 있다.
* 프로그래밍 언어에서의 2가지 Type System
(1) 정적 타입 (Static Type)
- 컴파일 시에 타입이 결정
- 변수를 선언할 때, 반드시 앞에 타입을 명시해야 하는 언어들은 모두 정적 타입 시스템에 속한다.
ex ) C, C++, Java ...
- 결국 장점에 대해 언급을 하자면,
. 컴파일 시에 타입에 대한 정보를 결정하기 때문에 속도↑ (효율성↑)
. 타입 에러로 인한 문제점을 초기에 발견할 수 있어 타입의 안정성↑
(2) 동적 타입 (Dynamic Type)
- 런타임 시에 타입이 결정
- 코드를 작성할 때, 변수 타입을 명시하지 않고 런타임에 변수의 값에 따라 타입이 결정되는 시스템.
ex ) Python, Ruby, SmallTalk ...
- 장점 : 유연성 (혹은 적응성)
런타임까지 타입에 대한 결정을 끌고 갈 수 있기 때문에 많은 선택의 여지가 있다.
- 단점 : 안정성
인터프리터 언어는 배우기는 쉬우나 실행 도중에 변수에 예상치 못한 타입이 들어와 Type Error 를 뿜는 경우가 생긴다.
그러니 너무 편리함에만 의존하면 안정성 저하를 유발할 수 있다.
* Type System 에 따른 상속의 의미 차이
객체지향에서 중요한 개념 중에 하나가 다형성(Polymorphism) 이고,
일반적으로 다형성은 함수의 오버라이딩, 즉 동적 바인딩에 의한 것이다.
(1) 정적 타입(Static Type) 의 상속
: 코드 재사용 + 타입의 호환성 유지 를 목적으로 한다.
- 컴파일 시에 상속의 관계를 파악하여 상속관계에 있는 객체들 간의 타입 호환성을 유지해
타입은 컴파일 시에 결정되나 메소드 호출 시에 값에 따라 그 객체의 메소드를 호출하게 된다.
이와 같은 방법으로 동적 바인딩에 대한 문제를 해결했다고 할 수 있다.
(2) 동적 타입(Dynamic Type) 의 상속
: 코드 재사용 을 목적으로 한다.
- 타입 속성에 따라 메소드의 형태만 같으면 동적 바인딩을 유도할 수 있다.
간단하게 예시를 보면,
a.call();
정적 타입의 경우 :
컴파일 시에 a 객체의 타입에 호환될 수 있는 타입을 결정하고,
런타임 시에 객체의 값에 따른 호환성 있는 객체의 메소드를 호출한다.
=> 즉, 상위 클래스의 코드 재사용과 동시에 타입의 호환성을 유지하는 목적을 가진다.
동적 타입의 경우 :
동적 타입 시스템에 의해 동적 바인딩이 자동적으로 유도되어 단지 상위 클래스의 코드를 상속받는 의미가 전부다.
바인딩이 일어나는 타이밍에 관련 포스팅 :
http://destiny738.tistory.com/178
휴.. 팔수록 어렵다 ㅠ_ㅠ
dynamic_cast 연산자 (0) | 2014.10.26 |
---|---|
.h 컴파일 : #ifndef vs. #pragma once (0) | 2014.08.25 |
RECENT COMMENT