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
열혈강의 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

열혈 강의 12장 : 템플릿


template <typename T>

T Add(T a, T b){

return a+b;

}


쉽게 말해 자료형을 결정 짓지 않는 것.


1. 함수 템플릿

2. 템플릿 함수  --> 함수 템플릿에 의해 실제 호출이 가능한 함수.


template <typename T>

void show(T a, T b){

cout<<a<<'  '<<b<<endl;

}


int main(){

show(5, 5.5);   --> 컴파일 에러, 어느 장단에 맞춰야 할지 모른다.

}


======================= 해결책


template <typename T1 , tepename T2>

void show (T1 a, T2 b){ ... }   --> 두개의 템플릿을 선언해 준다.



2. 함수 템플릿의 특수화


template <typename T>

void SizeOf(T a){

cout<<sizeof(a)<<endl;

}


int main(){

int a = 10;

float f = 10f;

double d = 10.0;

char* name = "name";


SizeOf(a);                       /// 4

SizeOf(f);                        /// 4

SizeOf(d);                        /// 8

SizeOf(name);                 ///4 --------> 포인터의 크기가 출력된다. 함수 내에서 return strlen(name); 이 반환되기를 원할 것이다.

}


============================해결책


tempalte<typename T>

void SizeOf(T a) { ... }


template<>                             -------------> 특수화, 원래는 한줄에 같이 쓴다. template<> void SizeOf(char* _name) { ... }

void SizeOf(char* _name){

return strlen(_name);

}


--> template<> void SizeOf<char*> (char* _name){ ... }  --> 올바른 선언 방법



3. 클래스 템플릿


template<typename T>   --> 다음에 정의하는 클래스를 템플릿화 하겠다.

class Data{                       --> 클래스가 아니라 템플릿이다.

private:

T data;

public:

Data(T a) { data = a; }

T GetData(){ return data; }

void SetData(T a){ data = a; }

};


int main(){

Data<int> d1(0);

Data<char> d2('a');

}


----> 객체 생성 시 결정하고자 하는 자료형을 명시적으로 선언해 주어야 한다.

--> 객체 생성 순서 --> 메모리 할당 --> 생성자 호출

--> 메모리 공간의 할당이 우선적으로 진행되어야 하기 때문에 T의 자료형을 알고 있어야 한다.

--> 때문에 명시적으로 선언을 해주어야 한다.


선언과 정의 분리


template<typename T>

Data<T>:: Data(T a) { data = a; }


template<typename T>

T Data<T>:: GetData() { return data; }


teplate <typename T>

void Data<T>:: SetData(T a) { data = a; }


--> 멤버 함수를 정의할 때마다 반드시 붙여 줘야 한다.

--> Data<T> --> 클래스 Data가 아닌 클래스 템플릿 Data를 의미한다.


4. 스택 클래스의 템플릿화


template <typenaem T> 

class Stack{

private:

int len;

T* data;

public:

Stack() : len (-1) { data = new T[100]; }

void push(T a);

T pop();

}


template <typename T>

void Stack<T>:: push(T a){ data[++len] = a; }

tempalte <typename T>

T Stack<T>:: pop() { return data[len--]; }



int main(){

Stack<int> a;

a.push(10);

a.push(20);


Stack<char> c;

c.push('a');

c.push('b');

}



5. 템플릿의 원리 이해

--> 템플릿 함수의 인스턴스화

--> 템플릿 클래스의 인스턴스화


template<typename T>

T Add(T a, T b){

return a+b;

}


--> 메인 에서 int형으로 호출했을 경우 

int Add(int a, int b){

return a+b;

}

--> 메인에서 double형으로 호출했을 경우

int Add(char a, char b){

return a+b;

}

                                 --> 와 같은 함수가 만들어 진다. --> 템플릿을 기반으로 함수가 만들어진다. (컴파일러에 의해)

--> 다음 번에 또 int 형으로 불려졌을 때, 다시 만들어지지 않고 만들어져 있는 놈이 불려온다.

--> double형으로 호출 했을 경우 위와 같이 double형으로 함수 템플릿이 인스턴스화 된다.

--> 클래스도 똑같은 원리


--> 이와 가이 함수 템플릿을 기반으로 실제 호출이 가능한 함수들을 가리켜 템플릿 함수라고 한다.

--> 함수가 만들어 지는 현상을 가리켜서 함수 템플릿의 인스턴스화 라고 부른다.


--> 템플릿은 컴파일러에 의해 처리된다.

--> 따라서 헤더와 정의를 분리할 수 없다. --> 분리 컴파일을 가능하게 하는 것은 링커이기 때문에

--> 하나의 파일 내에 정의와 선언이 같이 있어야 한다.





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

[C++] API란?  (0) 2016.12.15
[C++] 예외처리 // 열혈강의  (0) 2016.12.14
[C++] 복사생성자 정리  (0) 2016.12.07
[C++] 레퍼런스 정리  (0) 2016.12.07
[C++] 연산자 오버로딩 // 열혈강의  (0) 2016.12.07


C++를 공부하는 부분에 있어서

상속과 더불어 상당히 골치 아픈 부분 중 하나.

이번 포스팅에서 완벽하게 처리하고 가겠다.


1. 복사 생성자의 형태 --> 복사생성자도 생성자중에 하나


class AAA{

private:

int n;

public:

AAA(){}

AAA(int i) : n(i){};


AAA(const AAA& _a){

n = _a.n;

}

}


int main(){

AAA aaa(10);

AAA bbb(aaa);  ---> 멤버 변수가 복사된다.

}


---> 복사생성자도 생성자와 같이 디폴트 복사생성자가 존재한다. (얕은 복사)



2. 얕은 복사의 문제점, 깊은 복사


--> 클래스를 디자인할 때, 당장은 깊은 복사를 하지않더라도 확장성을 위해 (먼 미래를 위해)

복사생성자를 정의해두자


class AAA{

private:

char* name;

public:

AAA(char *_name) {

name = new char(strlen(_name)+1];

strcpy(name, _name);

}


~AAA(){ delete[] name }

};


int main(){

AAA aaa("kim");

AAA bbb(AAA);    --->> 디폴트 복사생성자가 호출된다. --> 오류가 난다 왜??

}


---> AAA 클래스 내의 멤버변수 name은 포인터로 힙 영역을 가리키고 있다.

따라서 bbb는 aaa가 가르키는 힙 영역을 그대로 가리키게 된다. (멤버 대 멤버 복사를 하기 때문) --> 주소값을 복사하게 된다.

--> bbb객체가 없어지면서 delete에 의해 메모리가 해제가 된다.

또한 여전히 그 공간을 가르키고 있는 aaa 객체에서도 같은 공간을 해제 하려한다. 

--> 하나의 메모리 공간을 두번 소멸하려 했기 때문 --> 문제 발생


AAA (const AAA& _a){

name = new char[strlen(_a.name)+1];

strcpy(name, _a.name);  

}


--> 디폴트 복사생성자는 얕은 복사를 제공하기 때문에, 깊은 복사를 사용자가 정의를 해주어야 한다.



3. 복사생성자의 호출시기


1. 

int main(){

AAA obj1(10);

AAA obj2(obj1);

}


기존의 객체가 새로운 객체를 초기화 하는 경우


2. 

void fct(AAA a){ ... };


int main(){

AAA obj(10);

fct(obj);             ----> 1. 메모리 공간 할당

2. 값 전달 --> 초기화 --> 값을 전달할 때 복사생성자가 호출된다.

}                                             --> 레퍼런스로 전달받으면 상관없는 일


함수에 매개변수로 전달이 될 때 (값에 의한 전달)


3. 


AAA fct(){

AAA a(10);

return a;

}


int main(){

fct();                 ---> 복사본이 리턴된다.    1. 함수 내의 a객체와 똑같은 형태로 메모리 공간 할당

fct().show();                                                2. 쓰레기값으로 초기화가 되어있다.

}                                                                          3. 복사본의 복사생성자를 호출하면서 a의 인자를 전달



함수 내에서 객체를 값에 의해 리턴




C++ 레퍼런스에 대해 정리해보는 시간을 가지자.

사실 정의만 놓고 봐서는 어려운 내용이 아니지만

정확하게 알고가지 않으면 나중에 상당히 혼동될 염려가 있을 것 같다.

이번 포스팅에서 완벽하게 정리하고 가겠다.



1. 레퍼런스란 쉽게 말해서 변수에 하나의 이름을 더 붙여주는 것. 하나의 메모리 공간에 여러 이름을 붙여줄 수 있다.


& 연산자는 포인터에 주소값을 전달하기 위해 사용할 수 있고

레퍼런스 선언을 위해 사용할 수 있다.


ex)

int a = 10;

int *ptr = &a;

int &ref = a;


변수란? 메모리 공간에 할당된 이름 --> 레퍼런스는 그러한 이름에 또 다른 이름을 붙여준다.

--> 그 이름으로도 변수를 원래대로 사용할 수 있다.


int i = 5;

int &j =i;


선언과 동시에 초기화가 되어야 하며

선언된 후에는 원래 변수와 완전 똑같이 사용 가능하다.

이름이 존재하지 않는 대상을 레퍼런스로 선언할 수 없다. (ex 상수)


int fct(){


int val = 20;

int &ref = val;

return ref;


}

--> int형 레퍼런스를 int형으로 반환해도 문제없다! --> 레퍼런스는 완전히 똑같은 것으로 생각하면 된다.

--> 만들어지는 방식만 차이가 날 뿐 똑같다.


반대로


int& Get(int& x){ return x; }


int main(){


int _x = 10;

int y = Get(_x);  ---> Get은 레퍼런스를 리턴받아서 바로 변수에 대입. --> 레퍼런스는 변수와 똑같이 사용가능하기 때문


}

------------------------------------------------------------------------------ex2


int i = 0;

int &ref1 = i;

int &ref2 = ref1;


--> i에 ref1 ,ref2라는 이름을 붙여주고 있다. (하나의 메모리 공간에 이름이3개 --> (C에서는 상상도 못하는 일)

--> 역시 문제 없다.


-------------------------------------------------------------------------------



2. call by value , call by reference


1. 포인터를 이용한 call by reference --> 포인터 연산에 의한 것이기 때문에 위험성이 존재한다.


void swap (int *xxx, int *yyy){

...

xxx++;  ---> 이러한 코드가 잘못들어갈 경우 심각한 오류가 생길 수 있다.

...

}



2. 레퍼런스를 이용한 call by reference --> 함수의 호출 형태를 구분하기 어렵다.


void swap (int &xxx, int &yyy){ ... }


int main(){

int a,b;

....

...

swap(a,b);             --> a,b 변수가 xxx, yyy 라는 별명으로 함수내에서 적용된다.

--> call by value인지 call by reference 인지 구분이 모호하다.

}



포인터 연산을 할 필요가 없으므로 보다 안정적이다.



3. call by value


void show(int _a){


cout<< _a <<endl;

}


int main(){


int a = 5;

show(a);    --> 변수a를 매개변수 _a에 복사하게 된다. --> 전달 내용이 커질 수록 복사량이 많아짐 --> 기능저하


}


void show(int &_a){ ... }

--> 레퍼런스를 이용하면 복사가 일어나지 않는다. --> 성능 향상

--> 레퍼런스를 이용할 경우 함수내에서 데이터가 조작될 가능성이 있다.


void show(const int &_a) { ... } --> 조작될 위험을 방지 (const)


-----------------------------------------------------------------------------------------------


3. 레퍼런스를 리턴하는 함수


int& GetInt(int &val){


return val;

}


int main(){


int n = 10;

int &ref = GetInt(n);  -----------> 1. n이라는 메모리 공간에 val이라는 이름을 붙여준다

---> 2. 레퍼런스를 리턴하는 함수이기 때문에 n은 ref라는 이름을 하나 더 갖게 된다. ( n = val = ref )

---> 3. 함수가 종료되면 함수 내의 레퍼런스도 일반 변수와 마찬가지로 스택에서 없어지게 된다. --> (이름만 없어짐, 메모리는 그대로)

---> 4. 결과적으로 n이라는 메모리 공간에 ref라는 이름을 하나 더 가지게 된다. (val은 함수 종료와 함께 없어짐)

}


------------------------------------------------------------------------------------------------------------------------------------


int GetInt(int &val){ 

...

return val;      ------------> int형으로 리턴된다. 위의 경우에 에러가 난다. 왜?? 레퍼런스는 상수에 정의를 할 수 없다!!

 } 


-------------------------------------------------------------------------------------------------------------------------------------


int& fct(){


int val = 10;

return val;


}


int main(){


int &ref  = fct(); ------------> 오류가 난다. 왜?? 함수가 끝나면 val이라는 메모리 공간은 없어지기 때문에

--> 없는 공간에 이름을 붙일 수 없다!


}


--> 지역변수는 레퍼런스로 리턴할 수 없다.

--> 지역객체 또한 레퍼런스로 리턴할 수 없다.



?? 객체 레퍼런스를 리턴하는 함수 대한 이해가 필요함  --> 복사생성자에 해답이 있는 듯 하다


AAA 클래스{

int x,y;


public:

AAA(int _x, int _y) : x(_x), y(_y){};


}


AAA Get(){


AAA temp(3,4);

return temp;

}


int main(){


AAA a(1,2);

AAA &f = GetA();     ---> 이게 왜 실행이 가능한거지?


}



AAA& Get(){


AAA temp(3,4);     -----> 이것도 실행가능한데 쓰레기 값이 들어간다.

return temp;


}


-------------------------------------


AAA& ref_AAA(AAA& _a){


return _a;

}

---------->> 일반 변수와는 다르게 클래스는 메인 함수에서 AAA &ref = GetAAA(a); 로 호출해도 문제가 없다. 왜??

AAA GetAAA(AAA & _a){            ---------->> 예를 들어 int형이라면 문제가 생긴다. 상수를 리턴하기 때문. 클래스는 뭐가 다른거지?


return _a;


}

열혈 강의 10장

static복습하고 오기. 전역함수로 선언(friend 선언 이해하기)


왜 연산자 오버로딩인가

함수 오버로딩이란 이르이 같아도 전달 인자에 따라 호출되는 함수가 달라지는 것

int a = 1+2;             --> 정수형 변수

Point p3 = p1+p2;  --> 객체

--> 같은 +인데도 피연산자의 종류에 따라서 다른 +기능을 한다.


1. 객체를 리턴하는 함수

1. AAA& GetName(){ return a; }

2.AAA GetName() { return a; }

--> 차이가 뭐지


class Point {

private:

int x, y;


public:

Point(int _x=0, int _y=0) : x (_x), y(_y) P{}

void show();

Point& operator++();

friend Point& operato--r(Point & _p);

}


void Point::show() {

cout <<x<<"  "<<y<<endl;

}


Point& Point::operator++() {  --> 왜 레퍼런스로 리턴을 받는가? 

x++;

y++;

return *this;  --> this는 포인터 *가 붙었기 때문에 자기자신을 리턴

}


Point& operator--(Point& _p){  --> 왜 참조로 리턴을 하는가?

_p.x--;

_p.y--;

return _p;

}


int main(){

Point p(1,2);

++p;

p.show();   ///2,3


--p

p.show();    ///1,2


++(++p);    ///3,4

p.show();


--(--p);

p.show();    ///1,2

}

----------------------------------------------------------------------------------문제 제기


Point Point::operator++(){

x++;

y++;

return *this;

}


--> 참조로 리턴을 받지 않는다면?? --> 나 자신을 복사해서 리턴을 한다.  --> 리턴과 동시에 새로운 객체를 만들게 된다. (복사본)

--> ++(++p)  --> 밑줄 친 부분이 p자기 자신이 아니라 복사본이 오게 된다.

--> 그 결과 p.show()에는 2,3 즉, p는 ++연산을 한번만 하게 되고, 괄호 밖의 ++연산은 p의 복사본에 하게된다. 

--> 복사생성자 호출조건 case3 참고


--> 조로 리턴을 받는다면?? --> ++(++p) --> 밑줄 친 부분에 복사본이 아닌 p의 레퍼런스가 위치하게 된다.

--> 그 결과 p에 ++연산이 두번 실행된다.


--> 함수를 void로 선언하지않고 Point&로 선언하는 이유이다.


check!! 

--> p3    =  p1   +   p1;

      p3    =  리턴된 객체  ---> 복사생성자에 의해 멤버 대 멤버 복사가 일어난다.


2. 단항 연산자 오버로딩 전위 후위


++p --> p.operator++();

p++ --> p.operator++(int);   --> int는 단순히 구분하기 위한 용도


Point operator++(int){

Point temp(x,y);            --> 증가시키기 전의 값을 저장

x++;

y++;

return temp;

}                           --> 참조를 리턴하지 않는 이유 --> 지역객체이기 때문에 레퍼런스로 리턴할 수 없다.


int main(){

Point p(1,2);

(p++).show();    --> temp를 리턴받았기 때문에 1 2 출력

p.show();           --> 증가된 x, y

}

--> 참조가 불가능 하기 때문에 (p++)++ 는 불가능



3. 교환 법칙


Point p(1,2);

p+3; ---> 가능

3+p; ---> 불가능

가능하게 하려면?


class Point{

private:

int x,y;

public:

Point operator+(int val);

friend Point operator+(int val, Point& p);   ---> friend는 연산자 오버로딩 외에는 사용하지 말자.

}


Point::Point operator+(int val){

Point temp(x+val, y+val);

return temp;

}


Point operator(int val, Point& p){                        Point operator(int val, Point& p){

Point temp(p.x+val, p.y+val);        ---->          return p+val;                                   --> 바꾸어주는 기능으로도 구현할 수 있다.

return temp;                                                 }

}


전역함수로 선언해줌으로써 해결된다.


4. 임시 객체


Point p(1,2);

Point (1,2);   ---> 임시 객체 생성방법  --> 임시 객체는 생성된 후 다음 줄에서 바로 소멸된다.


임시 객체를 만드는 이유는 뭐냐?

-->임시 객체는 생성과 소멸을 최적화 시킨다. 임시 객체를 쓸 수 있는 부분에서는 써주는 것이 좋다.

교환법칙의 예에서 임시 객체를 사용할 수 있는 부분을 찾아보자.


Point Point::operator+(int val){

return Point(x+val, y+val);  ---> 소멸이 되기 전에 리턴!!

}


5. 대입 연산자 오버로딩


class Point{ ... };

int main(){

Point b(1,2);

Point a = b   --->  AAA a(b);  ---> 복사생성자에 의한 대입연산은 생성될 땐 가능하지만


Point a(1,2);

Point b(10,20);


a=b;   ----> a(b) ---> 객체 생성 문장이 아님 --> 복사생성자에 의한 대입 연산 불가능

but 디폴트 대입 연산자가 제공되기 때문에 가능하다.

}


------------------------------------------------------------------디폴트 대입연산자 ex


Point& operator=(const Point& p){

x=p.x;

y=p.y;

return *this;

}


대입 연산자의 문제점


class Person{

private:

char* name;

public:

Person(char* _name){

name = new char[strlen(_name)+1];

strcpy(name,_name);

}

Person (const Person& p){

name = new char[strlen(p.name)+1];

strcpy(name,p.name);

}

~Person(){ delete[] name }

};


int main(){                                        스택           힙

Person a("ddd");                          a ---->  ddd

Person b("fff");                            b ---->  fff


a=b;   --------------->> 문제 발생

}


b가 가진값을 a에 복사해준다. a가 b가 가리키는 fff를 가리키게 된다. (복사생성자의 문제와 유사)

디폴트 대입연산자는 얕은 복사가 가지지 않는 또 다른 문제 하나를 더 가지고 있다.

--> 메모리 유출이 발생한다.

--> a가 원래 가리켰던 ddd가 힙에 계속 남아있게 된다.


--> 올바르게 동작하도록 바꿔보자!

1. a가 가리키던 영역을 해제시켜준다.

2. 메모리 할당을 다시해서 b와 같은 문자열을 할당해준다.


Person& operator=(const Person& p){

delete []name;                                              --> 메모리 해제

name = new char[strlen(p.name)+1];            --> 깊은 복사에 의한 할당

strcpy(name,p.name);


return *this;

}


Point!!

--> Point&을 통해 자기자신을 리턴하는 이유 --> p3 = p1 + p2; 와 같은 연산을 수행하기 위해

--> 생성자에서 동적할당을 하게 되면  1. delete

    2. 깊은 복사를 하는 복사생성자.

    3. 해제, 깊은 복사를 하는 대입 연산자.

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

[C++] 복사생성자 정리  (0) 2016.12.07
[C++] 레퍼런스 정리  (0) 2016.12.07
[C++] virtual 소멸자, virtual table // 열혈강의  (0) 2016.12.05
[C++] 상속에 대하여 // 열혈강의  (0) 2016.12.01
[C++] 기초4 // 열혈강의  (0) 2016.12.01

열혈 강의 9장


함수 포인터 공부하자.


oop 프로젝트 6단계

--> AAA 클래스 내의 함수 virtual void show() { ... }

BBB 클래스 내의 함수 virtual void show() { 


AAA::show();

....

....


}


---> 유용하게 쓸 일이 많으니 꼭 기억할 것!!



1. virtual 소멸자의 필요성


/////////////////////////////////////////// Base 클래스 AAA 선언


class AAA{


private:

char* name1;


public:

AAA(char* _name1) {


name = new char[strlen(_name1)+1];

strcpy(name, _name);

}

~AAA(){

delete []name1;

}

}


////////////////////////////////////////// AAA를 상속하는 Derived 클래스 BBB


class BBB : public AAA{


private:

char* name2;


public:

BBB(char* _name1, char* _name2) : AAA(_name){


name2 = new char(strlen(_name2)+1];

strcpy(name2, _name2);


}

~BBB(){

delete []name1;

}

}


////////////////////////////////////////////// 메인 함수


int main(){


AAA* a = new BBB("aaa", "bbb");  ///// 1번

BBB* b = new BBB("aaa","bbb");  ///// 2번


}



2번의 경우 :


소멸의 주체가 되는 포인터가 BBB 객체 포인터이다.

BBB의 소멸자 호출 후 AAA의 소멸자도 호출

--> 메모리 반환이 정상적으로 일어난다.


1번의 경우 :


소멸의 주체가 되는 포인터가 AAA 객체 포인터이다.

AAA의 소멸자만 호출하고 BBB의 소멸자는 호출하지 않는다.

--> 메모리 유출이 일어난다.


--> 가리키는 객체가 무엇이든 AAA의 포인터이기 때문에 AAA의 객체로 바라본다.



////////////////////////////////////////////////////////////// 해결책


---> 해결책 : virtual 소멸자를 이용한다!!


virtual ~AAA(){

delete[] name1;

}


소멸자를 호출하려 들어갔는데, 가상함수로 선언 되어있다,

때문에 AAA를 상속하는 BBB의 소멸자를 호출한다.

그리고 또 다시 AAA의 소멸자를 호출하게 된다.

--> 특성 그 자체로 이해하기!!



2. virtual table


멤버함수는 객체 내에 존재하지 않는다.

코드영역이란?


객체를 여러개 만들 경우 여러개의 멤버함수 또한 만들어지게 된다. --> 비효율적이다.


때문에 코드영역에 멤버함수를 단 하나만 만들어 놓고

객체들이 공유하며 사용한다


////////////////////////////////////////////////// ex


class AAA{


int n;


public:

void Add(){

n++;


}

}


Add() 함수가 코드 영역에 들어가게 된다.


단,

--> Add(A* a){

(a->n)++;

}


이러한 형태로 변형되어 저장되며, 호출할 경우 Add의 인자로 객체의 포인터가 전달되어진다.


////////////////////////////////////////////////virtual table


virtual table은 key 값과 value 값이 있다.

key값은 어떠한 함수인지 구분해주는 역할을 하고, value값은 그 함수의 주소값을 지닌다.



객체 내에서 함수가 단 하나라도 virtual로 선언될 경우 (1개 이상)

그 클래스 내의 모든 함수(virtual이 아니라도)의 정보를 가지는 virtual table이 만들어진다.


때문에 함수가 호출 될 때에는 virtual table을 거쳐서 코드영역에 있는 함수를 찾게 된다.


virtual을 선언하지 않을 경우 바로 코드영역의 함수를 찾았지만 virtual table을 거쳐서 함수호출을 하게 된다.(virtual이든 아니든) ---> 단점.

따라서 메모리 또한 추가된다. (4byte)


AAA클래스의 멤버함수 virtual void fct()

virtual void show();


AAA를 상속하는 BBB클래스의 멤버함수 virtual void fct()

void fct2();


BBB클래스에서 virtual이 선언 되었기 때문에 virtual  table이 만들어지게 된다.

그 테이블에는 fct(), fct2() 그리고 AAA를 상속했기 때문에 show()의 정보 또한 저장된다.


AAA클래스의 virtual table에는 fct()와 show()의 정보


AAA의 fct() BBB의 fct()는 virtual table에 저장된 주소값이 다르다 --> 오버라이딩 되었지만 다른 함수이기 때문


--> 때문에 fct()는 AAA의 virtual table에도 BBB의 virtual table에도 저장되게 된다.


다만 BBB의 virtual table에는 재정의 되기전의 fct()의 정보가 전혀 없다. --> 가상함수의 원리


AAA* a = new BBB(); 의 가상함수 원리

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

[C++] 레퍼런스 정리  (0) 2016.12.07
[C++] 연산자 오버로딩 // 열혈강의  (0) 2016.12.07
[C++] 상속에 대하여 // 열혈강의  (0) 2016.12.01
[C++] 기초4 // 열혈강의  (0) 2016.12.01
[C++] 기초3 // 열혈강의  (0) 2016.11.30

열혈강의 8장


객체 리스트를 만들 때

포인터를 사용하는 이유

: 미리 생성되어져 버림.


포인터 배열로 선언할 경우 할당될 시에 객체가 생성됨.


단순 오버라이딩 했을 때  a b c 선언에 따라 함수가 어떻게 나오나

버추얼로 했을 때 버추얼된 함수를 나타내는방법



1. 객체 포인터의 권한


class Character(){


public:

void fct1();


}


class Gunner() : public Character{


public:

void fct2();


}


class Sniper() : public Gunner{


public:

void fct3();


}


Base 클래스의 객체 포인터는 자기 자신의 객체 뿐만 아니라

Derived 클래스의 주소값을 가질 수도 있다.


IS - A 관계에 의한 이해

-->  스나이퍼와 건너는 캐릭터이다. 라고 말할 수 있다. 때문에 

Base클래스인 Character의 포인터는 건너와 스나이퍼를 포함 할 수 있다.


--> 반대로 캐릭터는 스나이퍼이다.

캐릭터는 건너이다. 라고 말할 수 없다.

게임에서 존재하는 캐릭터가 모두 스나이퍼일리는 없다.


따라서 스나이퍼 클래스와 건너 클래스의 객체는 캐릭터 클래스의 객체이다. 라고 말할 수 있다.


1 . Character* c = new Character();

2. Character* c = new Gunner();

3. Character* c = new Sniper();

///// Base 클래스의 객체 포인터는 하위 클래스의 주소값을 모두 저장할 수 있기 때문에 문제없는 선언이 된다.


4. Gunner* c = new Gunner();

5. Gunner* c = new Sniper();


6. Sniper* c = new Sniper();



1. Character 객체 타입으로 선언된 포인터가 Character 객체를 가르키고 있다.

--> 따라서 Character 객체 내의 fct1(); 함수만 호출가능하다.


2. 이번에는 Gunner객체를 가르키고 있다.

--> Gunner객체의 fct2() 함수의 호출이 가능할 것 같지만, Character 객체의 멤버 함수 fct1()만 호출 된다.


왜?? Character 객체 포인터는 가르키는 대상이 무엇이건 Character 객체 내의 멤버와 Character가 상속한 멤버에만 접근이 가능하다.


3. 위와 똑같은 이유 때문에 Character 객체의 멤버 함수 fct1()만 호출된다.


4. 이번 같은 경우는 Gunner객체의 포인터가 Gunner의 객체를 가르키고 있다. 

Character에 의해 상속되어지고 있기 때문에 Character의 fct1()와 Gunner의 fct2() 에 모두 접근이 가능하다.


5. 이번 경우에도 Gunner 객체의 포인터로 선언이 되었기 때문에

Gunner이 가지고 있는 fct2()와 상위 클래스 fct1()의 호출이 가능하다.


6. Sniper객체 포인터가 자기 자신을 가르키고 있다.

이 경우 Sniper 클래스는 Character 과 Gunner에 모두 상속되어지고 있기 때문에

fct1(), fct2(), fct3() 모두 접근 가능하다.




2. 함수 오버라이딩


class Character(){


public:

void fct();


}


class Gunner() : public Character{


public:

void fct();


}


class Sniper() : public Gunner{


public:

void fct();


}


1 . Character* c = new Character();

2. Character* c = new Gunner();

3. Character* c = new Sniper();

4. Gunner* c = new Gunner();

5. Gunner* c = new Sniper();

6. Sniper* c = new Sniper();


c -> fct();


1. Character 객체 내의 fct()가 호출된다. (당연한 말)

2. Character 객체 내의 fct()가 호출된다.

3. 마찬가지

4. Gunner의 멤버함수 fct()가 호출된다.

왜?? --> 이름이 같기 때문에 오버라이딩에 의해 Character의 fct()가 은닉되어 Gunner 객체의 fct()가 호출된다.


5. Gunner의 멤버함수 fct()가 호출

6. Sniper의 멤버함수 fct() 호출

--> 마찬가지로 함수 오버라이딩에 의해 Character, Gunner 의 fct()가 은닉되어 Sniper의 fct()가 호출 된다.




3. virtual


class Character(){


public:

virtual void fct();


}


class Gunner() : public Character{


public:

virtual void fct();


}


class Sniper() : public Gunner{


public:

virtual void fct();


}


1 . Character* c = new Character();

2. Character* c = new Gunner();

3. Character* c = new Sniper();

4. Gunner* c = new Gunner();

5. Gunner* c = new Sniper();

6. Sniper* c = new Sniper();


c -> fct();



1. Character의 fct();

2. Gunner의 fct();

--> Character의 객체 포인터임에도 불구하고 Gunner의 멤버함수가 호출된다. 왜??

가상함수로 선언되었기 때문에 어떠한 객체를 가르키던 오버라이딩된 함수만 호출되어지게 된다.

말그대로 '가상'함수이기 때문에


3. Sniper의 fct();   --->  fct는 가상함수이기 때문에 오버라이딩된 Sniper의 fct()가 호출된다.

4. Gunner의 fct();

5. sniper의 fct();

6. sniper의 fct();



--> 함수오버라이딩의 은닉의 기능과 더불어 virtual을 사용할 경우 재정의의 기능까지 할 수있다.


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

[C++] 연산자 오버로딩 // 열혈강의  (0) 2016.12.07
[C++] virtual 소멸자, virtual table // 열혈강의  (0) 2016.12.05
[C++] 기초4 // 열혈강의  (0) 2016.12.01
[C++] 기초3 // 열혈강의  (0) 2016.11.30
[C++] 기초2 // 열혈강의  (0) 2016.11.30

열혈강의 5장


복사생성자 정리해야함 꼭!!


class test{

private:


char *name;


public:


test(char* _name){


name = new char[strlen(_name)+1];

strcpy(name, _name);


}


}


char* 에 대한 이해


const


일반 함수

const 함수


우선 순위는 일반 함수가 더 높다.


=====================================


const 정리


1. const 멤버변수를 정의할 때에는 초기화 리스트를 사용해야 한다.


class aaa{

private:

const int x;


public:


aaa(int _a) : x(_a){};


}


--->> const로 정의 되었기 때문에 초기 생성자가 호출 될 때에는 이미 변수에 쓰레기 값이 들어가있어 재정의가 불가능!


2. const 멤버 함수


1. 멤버함수가 상수화되면 이 함수를 통해 어떠한 멤버변수의 변경이 불가능해진다.

2. 상수화된 멤버함수 내에서는 const함수를 제외한 모든 함수를 부를 수 없다.

3. 상수화된 멤버함수는 멤버변수의 포인터를 리턴받을 수 없다.



3. const 객체


상수화된 객체는 어떠한 멤버변수의 조작도 불가하며, 오직 상수화된 멤버 함수만 불러올 수 있다.

(일반 객체에서 상수화된 멤버함수를 불러오는 것은 가능하다.)


-->> const에 의한 함수 오버로딩이 가능하다.



=============================================


explicit 키워드를 자주 사용하는 습관도 필요할 수 있다.


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

[C++] virtual 소멸자, virtual table // 열혈강의  (0) 2016.12.05
[C++] 상속에 대하여 // 열혈강의  (0) 2016.12.01
[C++] 기초3 // 열혈강의  (0) 2016.11.30
[C++] 기초2 // 열혈강의  (0) 2016.11.30
[C++] 기초1 // 열혈강의  (0) 2016.11.28

열혈강의 3장


C++ 문자열에 대해서

char *a --> 함수 넘겨줄 때

void show(char *_a);


or


char a[20];

void show(char* _a);


이 부분 좀더 공부하고

char* 로 문자열을 받을 수 있는 이유

C++ string에 대해 공부하기



생성자 : 가급적 초기화 용도로 사용하기




생성자를 private로 선언하면 오류가 난다.

하지만 private로 선언하는 경우도 있다.


매개변수가 있는 생성자를 정의했을 경우

기본 생성자를 호출하려면 따로 정의를 해주어야 한다.


생성자에서 동적할당을 했을 때

소멸자에서 delete를 통해 효율적으로 해제 할 수 있다.


객체 배열에도 생성자를 이용한 초기화가 가능한가?

포인터는 스택에 있고 포인터가 가르키는 부분이 힙에 저장된거?

동적할당을 할때 a = new char[strlen(_a)+1] 해주는 이유


malloc 는 단순히 메모리를 할당해주는 것

new를 써야 생성자 호출이 이루어진다. - 162p

객체의 조건을 만족시키려면 생성자의 호출은 반드시 이루어져야 한다.



객체 포인터가 가르키는 객체의 멤버 변수, 함수 참조 ->


this는 자기 자신을 가르키는 포인터이다.


어떠한 변수이건 지역변수보다 우선일 수 없다.



char* a;

char a[20];


cin >> a;

왜 밑에꺼만 되지?


객체의 포인터 배열 선언 방법으로 하면

new를 써서 할당해주기까지 메모리를 안잡아먹나?

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

[C++] virtual 소멸자, virtual table // 열혈강의  (0) 2016.12.05
[C++] 상속에 대하여 // 열혈강의  (0) 2016.12.01
[C++] 기초4 // 열혈강의  (0) 2016.12.01
[C++] 기초2 // 열혈강의  (0) 2016.11.30
[C++] 기초1 // 열혈강의  (0) 2016.11.28

+ Recent posts