C++ API design 이란 책을 처음 펼쳐보았다.

처음 느낌으론 Effective C++ 상당히 유사하다.

하지만 더욱 알기 쉽게 표현되어 있다.

책 초반부에는 테크니컬한 부분이 없기 때문에

읽다가 중요하다 싶은 부분에 대해 포스팅을 진행해보기로 한다.


1. C++ API는 일반적으로 다음과 같은 요소들을 포함하고 있다.


API를 설계하고 구현하는 일은 대게 일반적인 앱의 개발보다 더 많은 작업을 요구한다.

API의 목적은 강력하고 안정적인 인터페이스를 다른 개발자에게 제공하는 데 있기 때문이다.

품질, 계획, 문서화, 테스트, 지원과 유지보수에 필요한 요구 수준이 높다.


-> 헤더

인터페이스를 정의하는  .h 파일의 모음으로 클라이언트는 인터페이스를 통해 코드를 컴파일 할 수 있다.

오픈 소스 API의 경우 API의 구현체인 소스 코드 (.cpp)가 포함 된다.

오픈 소스의 의미 --> 구현부를 클라이언트가 볼 수 있는 것.


-> 라이브러리

API를 구현하는 하나 이상의 정적 또는 동적 라이브러리를 말한다.

클라이언트는 필요한 기능을 추가하기 위해서 이 라이브러리를 참조한다.


-> 문서화

API를 어떻게 사용하는지 설명하는 개괄적인 정보를 가지고 있으며

대게는 API의 모든 클래스와 함수에 관해 자동으로 생성된 메타 데이터를 제공한다.


API 디자인 주의 사항

1. API가 상당히 광범위하게 사용되어 질 수 있다는 점을 명심해야한다.

2. API에서 발생되는 문제는 의존 관계를 맺는(API를 사용하는) 모든 앱에 영향을 미친다.

3. API를 변경할 때에는 반드시 하위 호환성에 신경 써야 한다. 

(이전 버전을 쓰고있는 앱을 위해)

4. API는 지속성이 강하다. 때문에 계획, 설계, 버전 관리 및 리뷰에 많은 비용을 투자해야 한다.

5. 사용자가 API에 대한 정보를 쉽게 얻기 위해 잘 정리된 문서화가 필요하다.

(사용자는 소스 코드를 볼 수 없기 때문에)

6. 테스트 자동화? 회귀 테스트? 안정적인 릴리즈


초기의 소프트 웨어 개발 --> 제품의 모든 코드를 작성

--> 요즘에는 상업용 라이브러리와 오픈 소스의 보급으로 개발된 기능을

재사용 하는 것이 일반적. 필요한 부분이 있다면 가져다 쓰면 끝.



1장을 읽는 내내 이게 뭔 뜬 구름 잡는 소린가 하는 느낌이 많이 든다.

그래서 그런지 포스팅도 두서 없이 주저리주저리 떠든 감이 있다.

일단은 쭉 읽어 나가보기로 한다.

오늘의 포스팅은 여기서 마치겠다.


'C++' 카테고리의 다른 글

[C++] 전방 선언  (0) 2016.12.19
[C++] 팩토리 함수  (0) 2016.12.19
[C++] 예외처리 // 열혈강의  (0) 2016.12.14
[C++] 템플릿 // 열혈강의  (0) 2016.12.12
[C++] 복사생성자 정리  (0) 2016.12.07


가급적 선행 처리자보다 컴파일러를 더 가까이 하자.

#define SIZE 10

--> 컴파일러에겐 보이지 않는다. 전처리기가 컴파일러에게 넘어가기 전에 숫자 상수로 대입해 버린다.

--> 기호 테이블에 들어가지 않는다.

--> 에러가 날 경우 SIZE라 표시 되지 않고 10으로 표시 된다 --> 디버깅이 어려움


해결책

const int Size = 10;

--> 매크로 대신 상수를 쓴다.

--> 컴파일러의 눈에도 보이며, 기호테이블에 들어간다.

--> 등장 횟수 만큼 사본을 만드는 매크로와는 달리 단 하나의 원본만 있으면 된다.


tip!!

상수의 정의는 대부분 헤더파일에 넣는다.

ex)

define.h 헤더 파일에 const double Width = 5.4;  정의를 해놓고

  cost double Height = 7.7;  


사용하고자 하는 파일에 가져다가 사용한다.

#include "define.h" 



#define를 상수로 교체할 때 주의점.


1. 상수포인터를 정의하는 경우


const char* const name = "Kim";

const stirng name("Kim");


참고)

1. char* name;    

2. const char* name --> 가리키는 대상을 상수화 시킨다.

3. char* const name --> 포인터 자체를 상수화 시켜 다른 객체를 가리키지 못하게 한다.

4. const char* const name --> 2,3 모두 해당


2. 클래스 멤버로 상수를 정의하는 경우


--> 상수의 유효범위를 클래스 내로 한정하고자 할 때 사용

--> 상수의 사본 개수가 한 개를 넘지 못하게 만들고 싶다면 static멤버로 만든다.


class Cost{

private:

static const int Size;    //  방법 1 : static const int Size = 10 --> 으로 해줄 수도 있으나 지원하지 않는 컴파일러가 있을 수 있다.

int arr[Size];

};

방법 2 : 

const int Cost::Size = 10;  --> static 멤버 초기화 문법 --> main호출 전에 실행된다. 다른 멤버 변수를 이렇게 정의할 수 없다.


====================================================================문제 제기


2번째 방법으로 정의를 해줄 경우 int arr[Size]; 에서 오류가 발생한다.


해결책 --> 나열자 둔갑술 (enum hack)


class Player{

private:

enum { Size = 10 };

int Item[Size];

};

--> 나열자 둔감술은 동작방식이 const보다 #define에 가깝다.

--> const는 주소값을 얻어 낼 수 있지만, enum은 주소값을 얻어낼 수 없다.

--> 따라서 상수를 가지고 주소를 얻는다든지 참조자를 쓴다든지 하는 경우를 방지할 수 있다.

--> enum은 #define처럼 어떤 형태의 쓸모없는 메모리를 할당하지 않는다. --> 그럼 const는 한단 말인가?

--> 나열자 둔갑술은 상당히 상용화된 기술이며 템플릿 메타프로그램의 핵심 기법이다.


클래스 상수를 #define로 만들 경우

--> 유효범위가 없다.

--> 컴파일이 끝날 때까지 유효하다.

--> 캡슐화의 개념이 없다. (private)

--> 따라서 임의의 클래스 안에서만 쓸 수 있는 매크로를 만들 수 없다.


메크로 함수


#define CALL_WITH_MAX (a,b) f((a) > (b) ? (a) : (b))   --> 인자마다 괄호를 씌워주어야 한다.


int main(){

int a = 5;, b = 0;

CALL_WITH_MAX(++a, b);               //a가 두 번 증가   --> 매크로 함수 내에서 a가 두번 불리게 된다.

CALL_WITH_MAX(++a,b+10);          //a가 한 번 증가

}


해결책 --> 인라인함수에 대한 템플릿


template<typename T>

inline void callWithMax(const T& a, const T& b){

f(a > b ? a : b);

}


1. 이 함수는 템플릿이기 때문에 동일 계열 함수군을 만들어낸다.

2. 구현의 어려움 ( 괄호와 같은 )도 없고 인자를 여러번 평가할 (위와 같은 문제) 요소가 사라졌다.

3. 진짜 함수이기 때문에 유효범위 및 접근규칙을 따를 수 있다.


동일 계열 함수군이란?

--> 하나의 템플릿으로 int, double, char 등 여러개의 함수가 만들어 질 수 있다.

      따라서 동일 계열 함수군은 하나의 템플릿으로 만들어 질 수 있는 모든 함수들을 통칭한다.



POINT!!

1. 단순한 상수를 쓸 때는, #define보다 const 객체 혹은 enum을 우선 생각하자.

2. 함수처럼 쓰이는 매크로를 만들려면, #define 보다 인라인 함수를 우선 생각하자.

열혈강의 13장 예외처리

---> 예외가 발생하는 지역과 처리되어지는 영역이 다른 경우

---> 기존 예외처리 방식은 예외가 방생하는 지역에서 해결을 한다.


1. 예외처리 메커니즘

try {

if(){ //예외 발생 예상 지역

throw ex;  --> ex를 가리켜 '예외'라고 표현한다. 예외상황이 발생됨을 알려주기 위해 사용되는 키워드

}

}

catch(처리 되어야 할 예외의 종류){ --> 위 try 블록의 ex가 전달된다.

//예외를 처리하는 코드

}


int main(){

int a, b;

cin >> a >> b;


try{

if(b==0) throw b;   ----> 예외가 발생하면 b가 catch로 전달 된다.

....                            -----> 예외가 발생하면 try블록안의 다음 문장들은 실행되지 않고 catch가 실행된다.

....                            -----> 반대로 예외가 발생하지 않으면 catch가 실행되지 않고 try블록을 끝까지 실행하게 된다.

}

catch(int exception){

cout<<exception<<"을 입력했습니다."<<endl;

cout<<"입력 오류! 다시 실행 하세요."<<endl;

}

}


2. Stack Unwinding (스택 풀기)


int divide(int a, int b);

int main(){

int a, b;

cout <<"두개의 숫자 입력 : "; cin >>a>>b;


try{

cout<<divide(a,b)<<endl;

}

catch(int exception){

....

....

}

}

int divide(int a, int b){

if(b==0) throw b;    ----------> b가 diveide가 호출되는 영역으로 넘어간다.

return a/b;

}


////////////////////////////////////////////////////////////////////////


int main(){ 

try{ 

divide();

....

}

catch(int ex) { .... }

}

int divide(){ divide2(); }

int divide2(){ divide3(); }

int divide3(){

throw b;      -------->

}


스택

divide3()   ---> 예외 발생 throw b; try catch 블록이 없네? 자신을 호출한 함수를 찾아간다.   ---> 전달해주면서 할당된 스택영역 해제

divide2()   ---> b를 전달 받음. try catch 블록이 없네? 자신을 호출한 함수를 찾아간다.          ---> 이 또한 마찬가지

divide1()   ---> b를 전달 받음. try catch 블록이 없나? 자신을 호출한 함수를 찾아간다.          ----> 이 또한 마찬가지

main()    ---> b를 전달 받음. try catch 블록이 있네? 예외처리 실행.


3. ex


1. throw 를 통해 예외를 전달 했으나 어디에도 try catch 블록이 없을 때 --> abort()함수가 호출.

   --> 예외는 반드시 처리되어야 한다!!


2. throw b;  ---> b는 int 형

   catch(char ex) { ... }   --> abort()함수가 호출된  --> 발생되는 타입과 처리되어지는 타입이 같아야 한다.


3. throw (int, double, char*)    ---> int , double, char* 형태로만 예외를 발생시키겠다!!


4. throw      --> 다양한 타입으로 예외를 발생시킬 경우

catch(char exp){ ... }

catch(int exp) { ... }

---> 함수 오버로딩의 형태가 아니라 순차적으로 물어보면서 자기 타입에 맞는 catch를 찾는다.


3. 예외 클래스 디자인

--> 예외는 클래스의 객체로 표현되어 지는 것이 일반적이다.

--> throw 예외객체 --> 일반적인 형태  --> 객체로 표현되어질 경우 예외가 왜 발생되어 졌는지에 대한 상황을 충분히 담을 수 있다.


------------------------------------------------------------- 주의 사항

class AAA{}

class BBB : public AAA{}

class CCC : pubic BBB {}


int main(){

try{

throw AAA();

throw BBB();

throw CCC();

}

catch(AAA& ex) { ... }   ---> 위의 세 경우 모두 이 블럭이 실행된다. 왜?? is-a관계에 의해 derived클래스가 모두 처리되어 질 수 있기 때문이다. (CCC는 AAA다가 성립)

catch(BBB& ex) { ... }    ---> 해결챌은 간단하다. catch의 순서를 거꾸로 해주면 된다.

catch(CCC& ex) { ... }   ---> AAA는 CCC이다가 성립하지 않기 때문에 AAA는 자신의 catch를 찾아 마지막까지 내려오게 된다.

}



-->throw AAA() --> 임시객체로 던졌는데 어떻게 참조 형식으로 받을 수 있지? 임시객체는 다음 줄이면 바로 없어져 버리는 거 아닌가?

'C++' 카테고리의 다른 글

[C++] 팩토리 함수  (0) 2016.12.19
[C++] API란?  (0) 2016.12.15
[C++] 템플릿 // 열혈강의  (0) 2016.12.12
[C++] 복사생성자 정리  (0) 2016.12.07
[C++] 레퍼런스 정리  (0) 2016.12.07

+ Recent posts