'함수 템플릿'에 해당되는 글 1건

  1. 2012.04.23 Android Framework 분석을 위한 C++ 1일차
프로그래밍/C++2012. 4. 23. 17:43

아래 내용은 Android Framework 분석을 위한 C++ 강의를 정리한 내용으로 사용된 모든 소스는 강의에서 나옴을 밝힙니다.

 

개발환경:

Visual Studio 2010 Express

 

참고하기에 좋은 문서/책:

문법: Effectiv c++

template c++ template programming

디자인 패턴 : GoF's 디자인 패턴

 

visual c++ 개발환경에서 기본적인 빌드/실행: 

build                  : F7

build + execute   : ctrl + F5

 

1. 헬로 월드

 

Visual Studio 2010 Express에서 리소스 부분의 .cpp 파일을 클릭해서 빌드에서 제외 하면 컴파일을 안 시키고 다른 소스를 계속 추가해서 실험해 볼 수 있음.

 

 

2. 함수 오버로딩

console에서 컴파일을 할 때, 리눅스의 경우 gcc를 사용하지만, 윈도우는 cl.exe를 사용하며 다음과 같이 컴파일 한다.

cl filename.cpp

cl filename.cpp /FAs  //assembly 소스를 만들어 달라고 요청

 

이제 오버로딩을 보자.(asm파일을 쉽게 보기 위해, iostream은 제외했다)

 

asm파일을 열어보면, 오버로딩 된 함수에 대한 이름이 각기 다른 것을 알 수 있다. (함수오버로딩의 결과물)

보다시피 int 인자를 가진 함수는 ?square@@YYHH@Z 이고, double 인자를 가진 함수는 ?square@@YANN@Z라고 보인다.

 

그럼 다음과 같이 하면 어떨까?

 

컴파일 시에는 각기 컴파일을 한 뒤 링크 과정을 거침으로, c와 c++의 혼합 컴파일 시, c에 정의된 함수는 오버로딩을 지원하지 않는 관계로 name mangling(컴파일 시 같은 오버로딩함수들의 이름을 다른 심볼 이름으로 교체)현상이 없고, 이로 인해 cpp파일의 컴파일 후, 링킹 과정에서 위에서 본 바와 같이 ?sqaure형식의 심볼이름을 찾으려다 못 찾아서 에러가 난다.

 

 

이를 해결하기 위해선, 함수 앞에 extern "c"를 붙여 주면 된다.

그러나, 만약 오버로딩함수2.cpp를 c로 변형한다면 역시나 에러가 난다. 이는 extern "C"는 cpp에서만 지원하기 때문이다.

 

 

cpp에서는 ifdef __cplusplus내의 모든 함수에 extern "C"가 붙고 c의 경우 제거된다.

 

결론은, header파일에 __cplusplus 라는 매크로를 쓰고, c나 cpp에서 부르면 된다. 

 

우리가 자주 쓰는 stdio.h에서도 이와 같은 매크로가 있어서 우리가 c 이던  c++ 이던, 상관하지 않고 쓸 수 있다.

 

추가적으로 overloading 된 함수를 찾는 순서는 다음과 같다.

1. Exactly matching.                        인자와 정확히 들어 맞는 type이 있는 함수를 먼저 찾는다.

2. Promotion.                                  인자가 변환될 때 data의 손실이 없는 타입을 가진 함수가 있는지 찾는다.

3. Standard Conversion                   암시적인 변환이 가능한 타입을 가진 함수를 찾는다.

4. user defined conversion operator  사용자가 만든 변환 연산자가 있으면 변환 후, 함수를 호출한다.

5. '...'                                           가변인자를 갖는 함수는 type에 무관하므로 이를 호출한다.

 

3. 인라인함수

 

위의 코드를 console에서 컴파일 할 경우, 그냥 하면 inline이 컴파일러에 의해 무시된다.  제대로 할려면 다음과 같이 해보자.

 

cl.exe fileName.cpp /Ob1 /FAs  // 인라인을 쓰겠다고 컴파일러에게 알려준다. 그럼 이렇게 생성된 asm파일을 보면,

 

 

우리가 일반적으로 inline을 쓸 경우, 속도가 빨라지나 obj 파일이 커질거라고 생각하나, 간단한 inline함수의 경우 오히려 크기가 작아진다.(asm파일을 참조하라)

 

console이 아닌 Visual C++ IDE에서 컴파일 시, debug옵션이면 inline을 거부하나, release일 경우 inline옵션으로 컴파일 되어 진다.

 

 

inline함수를 쓸 때 주의해야 할 점이 있다. 다음을 보자.

보통 헤더파일에 선언을 하고, 소스파일에서 구현을 하기 때문에, 인라인함수도 그런 식으로 한다면? 빌드 에러난다.  왜냐면, 인라인 함수에 대한 치환은 컴파일러가 담당하므로, 컴파일시, 인라인 함수에 대한 정의가 완전하게 나와야 한다.  즉 인라인 함수에 대한 정의가 치환 시점 전에 있거나, include되는 헤더파일 안에 있어야 한다.  다시 말하면, inline함수는 internel linkage를 갖는다.

 

참고로,

 

internal linkage : 임의의 심볼이 선언된 컴파일 단위(파일)에서만 사용가능.  internal linkage는 주로 헤더에 만든다.

예) static 전역변수, 인라인 함수, 매크로 상수/함수, 함수/클래스 템플릿, 구조체

 

external linkage : 임의의 심볼의 프로젝트내의 모든 컴파일 단위에서 사용가능

예) 전역변수, 일반함수

 

예제)

template<typename T> T square(T a){return a * a;}    internal linkage

int a;                                                                     다른 파일에서 extern int a;라고 선언 후 접근 가능

static int b;                                                             다른 파일에서 접근 불가.

const int c = 0;                                                       .c에서는 external

                                                                            .cpp에서는 internal

#define    MAX 10                                                   internal linkage

struct People {};

 

 

4. 함수 템플릿

 

c의 경우 필요시 코드 생성 기술을 다음과 같이 활용한다.

 

 

C++에서는 함수 tempalte을 써서 컴파일러가 처리할 수 있다.

 

반면에 template은 잘못 사용하면, 다음과 같은 결과가 생긴다.

 

 

한편 다음과 같은 경우는 어떨까?

 

T가 결정되어서 함수 템플릿이 진짜 함수가 되는 과정: "인스턴스화"라고 하고 "명시적 인스턴스화" 와 "암시적 인스턴스화"가 있다.

이렇게 위와 같이 <int>로 프로그래머가 정한 것을 명시적 인스턴스화 라고 한다.

암시적 인스턴스는 컴파일시 컴파일러에 의해 결정되는 것을 말한다.

 

 

5. 캐스팅

한마디로 C에서의 캐스팅은 위험하다.  물불 안 가리고 캐스팅을 하기 때문이다.  자세한 내용은 밑의 소스 코드를 참조하자.

 

//---------------------------------------------

출처: http://ikpil.com/262

static_cast 는 C 스타일 캐스트와 똑같은 의미와 형변환 능력을 가지고 있는, 기본적인 캐스트 연산자입니다. C 스타일의 그것과 구실이 똑같다 보니 받는 제약도 똑같습니다. 예를 들어,struct 를 int 타입으로 바꾼다든지 double을 포인터 타입으로 바꾸는 일은 이것으로 할 수 없습니다. 게다가, static_cast 는 표현식이 원래 가지고 있는 상수성(constness)을 떼어버리지도 못합니다. 이런 일을 하는 캐스트 연산자인 const_cast 가 따로 있는 것을 보면 짐작할 수있지요.
나머지 세 가지의 C++ 캐스트 연산자는 좀 더 구체적인 목적을 위해 만들어졌습니다. const_cast는 표현식의 상수성이나 휘발성(volatileness)을 없애는 데에 사용합니다. 이 연산자가 쓰여진 소스를 만나면, 아, 이 개발자는 const 나 volatile로 선언한 변수라든지 이런 타입의 값을 내는 표현식에서 이런 특성만 바꾸고 싶어하는구나 라고 생각하면 되겠습니다. 이러한 프로그래머의 의도는 컴파일러에 의해서 더욱 확실해집니다. 즉, 상수성이나 휘발성을 제거하는 것 이외의용도로 const_cast 를 쓰면 통하지 않습니다.

구체적인 용도를 가진 C++ 캐스트 연산자 두 번째는 dynamic_cast 입니다. 이 연산자는 상속
계층 관계를 가로지르거나 하향시킨 클래스 타입으로 안전하게 캐스팅할 때 사용합니다. 말하자
면, dynamic_cast 는 기본 클래스의 객체에 대한 포인터나 참조자의 타입을 파생(derived) 클래
스, 혹은 형제(sibling) 클래스의 타입으로 변환해 준다는 것입니다3). 캐스팅의 실패는 널 포인터
(포인터를 캐스팅할 때)나 예외(참조자를 캐스팅할 때)를 보고 판별할 수 있습니다

네 가지 C++ 캐스트 연산자의 마지막은 reinterpret_cast 입니다. 이 연산자가 적용된 후의변환 결과는 거의 항상 컴파일러에 따라 다르게 정의되어 있습니다. 따라서, 이 연산자가 쓰인소스는 직접 이식이 불가능합니다.
reinterpret_cast 의 가장 흔한 용도는 함수 포인터 타입을 서로 바꾸는 것입니다. 예를 들어, 어떤 특정한 타입의 함수 포인터를 배열로 만들어 놓았다고 가정합시다.
typedef void (*FuncPt r) ( ) ; // FuncPtr 은 인자를 받지 않고
/ / void를 반환하는 함수에 대한
/ / 포인터입니다.

FuncPtr funcPtrArray[10]; // funcPtrArray는 10개의 FuncPtr로 만들어진 배열입니다.
이때 다음의 함수에 대한 포인터를 funcPtrArray에 넣어야 할 피치 못할 사정이 생겼습니다.
int doSomething();
간단할 것 같지만 캐스팅을 하지 않으면 절대로 안 됩니다. 왜냐하면 doSomething은 funcPtrArray에 넣기에는 타입이 맞지 않기 때문입니다. 이 배열에 들어가는 함수는 void를 반환하지만, doSomething은 i nt 를 반환하지 않습니까?
funcPtrArray[ 0 ] = &doSomething ; / / 에러입니다! 타입불일치이군요.
reinterpret_cast 를 쓰면 컴파일러에게 이 일을 강제로 시킬 수 있습니다.
funcPtrArray[ 0 ] = / / 이것은 컴파일됩니다.
reinterpret_cast <FuncPtr> (&doSomething) ;

 

//----------------------------------------------

 

6. 레퍼런스

 

변수 선언을 했다고 치자.

그럼 메모리가 할당되고, 이를 만약 포인터 변수를 써서 assign했다 치자. 그럼 역시 변수를 가리키는 주소를 가지는 포인터가 메모리에 생긴다.  이와는 달리 레퍼런스의 경우, 별다른 메모리를 차지않고 기존 메모리에 대한 별명이라고 보면 된다.

 

 

 

다음을 보자.

 

답은 다음과 같다.

foo()를 호출 했을 때, p를 리턴하는데, 이는 임시객체를 리턴한다.  그러므로, 위에 정의된 구조체에 영향을 미치지 않는다.

당연히 p.x를 출력하면, 1이 출력된다. 

 

한편 임시객체를 리턴할 경우, 생성자/소멸자 등이 불리우고 메모리가 잡히기 때문에 성능에 큰 영향을 미친다. 

그럼 어떻게 해야 할까?  다음과 같이 레퍼런스를 사용하면 된다.

 

 

정리하자면,

임의의 함수가 값을 리턴하면,
     1. built in type인 경우, (ex: int foo() )       : 상수로 리턴된다.
     2. user defined type인 경우, (ex: Point foo() ) : 임시객체가 리턴된다.

 

어떤 함수의 리턴값이 참조라면
     1. built in type  (int& foo) : 함수 호출을 lvalue에 놓게다는 의미
         foo() = 10;
     2. user type ( Point& foo() ) : 임시객체를 제거하겠다는 의미!

 

 

참조 변수와 함수 인자

여기서 정리하고 가는 단계로 다음의 코드를 보자.

 

주의할 점은 inc3(z)로 z를 패스하는데, &z를 패스하지 않도록 주의한다.

이렇게 보면 레퍼런스로 받는게 코드 복잡성에서도 나아보이고, 메모리 구조면에서도 좋아보이나, 코드 가독성 측면에서 포인터가 더 좋다.( inc3(z)를 본다면, z가 변할지 안 변할지 알아보기 힘들다. 그러나 inc2(&y)를 보면 딱 봐도 y가 변할 거라는게 보이지 않는가?)

 

그러나 레퍼런스 인자를 쓸 일이 있는데 다음을 보자.

 

 

한가지 재밌는 것은, 다음의 경우다.

위에서 막 레퍼런스를 얘기했으니 2번 같겠지만, 1번이다.  int같은 경우 overhead가 크게 일어날 경우도 없고, const T&를 쓸 경우, 컴파일러가 최적화를 할 수 가 없다.

 

혼돈스러우니, 정리하자면,

함수가 값을 변경하지 않는다면 const T&를 사용하자.
  (A) built in type의 경우 : call by value 가 좋다.

         foo( int a )
  (B) user type 의 경우    : const T& 를 사용하자.
         foo( const Data& a )

 

 

 

7. Namespace

 

다음과 같이 코드를 짜다보면 같은 이름을 쓰고 싶은데 중복될까봐 이런 식으로 쓰게 되는 경우가 있는데,

이걸 매번 확일 할 수도 없고, 그래서 사용할 수 있는게 namespace이다.

 

이렇게 같은 함수 이름을 중복해서 써도, namespace만 정의 잘하면 문제 없이 쓸 수 있다.

 

참고로, #include <iostream.h> 에는 cout, endl이 전역공간에 있다. 그러나 <iostream>의 경우,

namespace std

{

   // cout, endl이 여기 있음.

}

와 같이 되어 있다.

 

한가지 더 알면 좋은 거 추가...

 

 

8. OOP 개념

 

다음의 코드를 보자.

 

 

복소수를 리턴하기 위해 복잡하게 리턴을 하는 것보다, 구조체를 만들어 보자.

 

 

다음은 OOP개념을 익히기 위해 기본적인 C의 방법부터 코드를 수정하는 과정이다.

 

 

 

9. 생성자 정리

 

 

'프로그래밍 > C++' 카테고리의 다른 글

Android Framework 분석을 위한 C++ 4일차  (0) 2012.04.26
[C++] Smart Pointer  (0) 2012.04.25
Android Framework 분석을 위한 C++ 3일차  (0) 2012.04.25
swap 함수  (0) 2012.04.25
Android Framework 분석을 위한 C++ 2일차  (0) 2012.04.24
Posted by code cat