만약 우리가 클래스를 만들때, 클래스안에 아무것도 넣지 않았다면, 컴파일러가 복사 생성자(copy constructor), 복사 대입 연산자(copy assignment operator), 소멸자(destructor)를 저절로 선언해 주게 됩니다. 이때 컴파일러가 만드는 함수의 형태는 모두 기본형이고 public 멤버 이면서 inline 함수 입니다. 
  1. class Empty(){};  

 즉, 위와 같이 선언된 클래스는 아래와 같다는 이야기 입니다. 
  1. class Empty{   
  2. public:   
  3.     Empty() {}                 //기본 생성자   
  4.     Empty(const Empty& emt){}  //복사 생성자   
  5.     ~Empty(){}                 //소멸자    
  6.   
  7.     Empty& operator = (const Empty& emt){} //복사 대입 연산자   
  8. };  
기본적으로 생성자와 소멸자는 클래스에 생성자나 소멸자가 없을때 자동생성이 되고, 복사 생성자나 복사 대입 연산자는 미리 존재하는 해당 함수가 없는데 아래와 같은 코드 상에서 그러한 함수를 호출하는 부분이 있다면 자동 생성이 됩니다. 
  1. Empty e2(e1)  //복사 생성자   
  2.   
  3. e2 = e1;      //복사 대입 연산자  
 기본 소멸자  
 : 컴파일러가 만드는 기본 소멸자에서 주의할 특성은 바로, 소멸자의 가상성 즉, virtual의 여부는 부모 클래스를 따라간다는 것이다. 이는 항목7에서 다시 다룬다.

 복사 생성자 / 대입 연산자  
 : 컴파일러가 자동적으로 만들어낸 복사 생성자/ 대입 연산자가 하는 일은 원복 객체의 비정적 데이터를 사본 객체쪽으로 그냥 복사하는 일이 전부입니다.  아래의 클래스를 보시죠. 
  1. template <typename T>   
  2. class NameObject{   
  3. public:   
  4.     NameObject(const char* name , const T& value);   
  5.     NameObject(const std::string& name, const T& value);   
  6.   
  7. private:   
  8.     std::string nameValue;   
  9.     T objectValue;   
  10. };  
 여기 이 NameObject는 인자값을 가지고 있는 생성자가 존재합니다. 이렇게 생성자가 선언되어 있으므로, 컴파일러는 기본 생성자를 만들어내지 않습니다. 이 클래스는 생성자는 있는데, 복사 생성자 /  대입연산자는 선언되어 있지 않기 때문에, 만약 프로그래머가 이것을 사용한다고 하면, 이것의 기본형이 컴파일러에 의해 만들어 집니다. 

컴파일러는 경우에 따라 클래스에 기본 생성자, 복사 생성자, 복사 대입 연산자, 소멸자를 암시적으로 만들어 놓을 수 있다.

Posted by 모과이IT
,
대입과 초기화를 구분하자  
  1. class B {...};   
  2. class A {   
  3. public:   
  4.     A(const std::string& name, const std::string& address, const std::list<B>& phone);   
  5. private:   
  6.     std::string theName;   
  7.     std::string theAddress;   
  8.     std::list<B> thePhone;   
  9.     int num;   
  10. };   
  11.   
  12. A::A(const std::string& name, const std::string& address, const std::list<B>& phone)   
  13. {   
  14.     thName = name;   
  15.     theAddress = address;   
  16.     thePhone = phone;   
  17.     num = 0;   
  18. }  
  C++규칙에 의하면 어떤 객체이든 그 객체의 데이터 멤버는 생성자의 본문이 실행되기 전에 초기화 되어야 한다고 명시되어 있다. 위의 예제는 초기화가 아니라 대입이다. 초기화는 진작 지나갔다. A의 생성자에 진입하기도 전에 기본 생성자가 호출된 것이다. 하지만 num은 미리 초기화 되지 않았다. 기본제공 타입의 데이터 멤버이기 때문이다. 생성자 안에서 대입되기 전에 초기화 되리란 보장이 없다. 따라서 이런경우 아래와 같이 초기화 리스트를 사용한다.
  1. A::A(const std::string& name, const std::string& address, const std::list<B>& phone)   
  2.     : theName(name), theAddress(address), thePhone(phone), num(0)    
  3. {   
  4.   
  5. }  
사용자가 원하는 값을 주고 시작하는 점은 같지만, 더 효율적일 가능성이 크다. 초기화 리스트에 들어가는 인자는 바로 데이터 멤버에 대한 생성자의 인자로 쓰이기 때문이다. 기존 방법은 기본 생성자를 호출하고 복사대입 연산자를 호출하는 방식이지만 초기화 리스트를 사용하면 복사생성자를 한 번 호출하므로 더 효율적이다. 기본타입은 상관 없지만 초기화 리스트에 넣어 두는 쪽이 좋다.

 기본제공 타입의 멤버를 초기화 리스트로 넣는 일이 의무일 경우가 있다. 이것은 바로 상수이거나, 참조자로 되어 있는 데이터 멤버의 경우엔 반드시 초기화되어야 하기 때문이다. 상수와 참조자는 대입 자체가 불가능하기 때문이다.

※ 객체를 구성하는 데이터의 초기화 순서
1. 기본 클래스는 파생 클래스보다 먼저 초기화 된다. 
2. 클래스 데이터 멤버는 그들이 선언된 순서대로 초기화 된다. 

※ 비지역 정적 객체의 초기화 순서는 개별 번역 단위에서 정해진다.
 - 정적객체(static object) : 자신이 생성된 시점부터 프로그램이 끝날때 까지 살아 있는 객체.
 - 정적객체 범주에 들어가는 것
  1. 전역 객체
  2. 네임스페이스 유효범위에서 정의된 객체
  3. 클래스 안에서 static으로 선언된 객체
  4. 함수 안에서 static으로 선언된 객체
  5. 파일 유효범위에서 static으로 정의된 객체

  이들중 4번은 지역 정적 객체라고 하고, 나머지는 비지역 정적 객체라고 한다. 다섯종류 모두 프로그램이 끝날때 자동으로 소멸된다. 즉, main() 실행이 끝날때 정적 객체의 소멸자가 호출된다. 

※ 서로 다른 번역 단위에 정의된 비지역 정적 객체들 사이의 상대적인 초기화 순서는 정해져 있지 않다.  설계에 약간의 변화만 주면 이 문제를 사전에 봉쇄할수 있다. 비지역 정적 객체를 하나씩 맡는 함수를 준비... 이안에 각 각체를 넣는다. 함수속에서도 정적 객체로 선언.
  함수에서는 이들에 대한 참조자를 반환하게 만든다. 그리고 함수 호출로 대신한다. 지역 정적 객체는 함수 호출 중에 그 객체의 정의에 최초로 닿았을 때 초기화 되도록 만들어져 있다. 

 마무리...  
 - 기본제공 타입 객체는 직접 초기화 하자.
 - 객체의 모든 부분에 대한 초기화에는 멤버 초기화 리스트를 사용하자.
 - 여러 번역 단위에 있는 비지역 정적 객체들의 초기화 순서 문제는 피해서 설계하자.  -> 비지역 정적 객체를 지역 정적 객체로 바꾸면 된다. 
Posted by 모과이IT
,
 우선 이번 항목에서는 쟁점이라고 할 수 있는 것은 Pass by Value(Call by value) 의 두가지 문제점을 제기 하는데요. 첫번째는 바로 고비용 문제 입니다. 
  기본적으로 C++는 함수로부터 객체를 전달받거나 함수에 객체를 전달할 때 값에 의한 전달 방식을 사용하는데요. 특별히 다른 방식을 지정하지 않는 한, 함수 매개변수는 실제 인자의 '사본'을 통해 초기화되며, 어떤 함수를 호출 한 쪽은 그 함수가 반환한 값의 사본을 돌려받습니다. 이들 사본을 만들어 내는 곳이 바로 복사 생성자인데, 이런 점 때문에 고비용의 연산이 되기도 합니다.

  한번 예제를 보면서 알아 보도록 하겠습니다. 
  1. #include <iostream>   
  2. #include <string>   
  3. using namespace std;   
  4.   
  5. class person{   
  6. public:   
  7.     person() : m_strName("name"), m_strAddress("address")   
  8.     {   
  9.         cout<<"person default con call"<<endl;   
  10.     }   
  11.     person(const person& rhs)   
  12.     {   
  13.         this-> m_strName = rhs.m_strName;   
  14.         this-> m_strAddress = rhs.m_strAddress;   
  15.   
  16.         cout<<"person copy con call"<<endl;   
  17.     }   
  18.     virtual ~person()   
  19.     {   
  20.         cout<<"~person call "<<endl;   
  21.     }   
  22. protected:   
  23.     string  m_strName;   
  24.     string  m_strAddress;   
  25. };   
  26.   
  27. class student :public person   
  28. {   
  29. public:   
  30.     student() : m_strSchoolName("school")      
  31.     {      
  32.         cout<<"student default con call"<<endl;    
  33.     }   
  34.     student (const student& rhs)   
  35.     {   
  36.         this ->m_strName = rhs.m_strName;   
  37.         this->m_strAddress = rhs.m_strAddress;   
  38.         this->m_strSchoolName = rhs.m_strSchoolName;   
  39.         cout<<"student copy con call"<<endl;   
  40.     }   
  41.   
  42.     ~student()   
  43.     {   
  44.         cout<<"~student call"<<endl;   
  45.     }   
  46. private:   
  47.     string m_strSchoolName;   
  48. };   
  49.   
  50. bool validatestudent (student stu); //student를 값으로 전달받는 함수   
  51.   
  52. int main()   
  53. {   
  54.     student stu;   
  55.   
  56.     cout<<"-------- pass by value ----------"<<endl;   
  57.     validatestudent(stu);   
  58.     cout<<"---------------------------------" <<endl;   
  59.   
  60.     return 0;   
  61. }   
  62.   
  63. bool validatestudent(student stu)   
  64. {   
  65.     return true;   
  66. }  
실행결과 :


 위의 예제를 실행해 보면 Pass by Value방식에는 문제가 없습니다. 실행을 해보면 생성자와 소멸자가 제대로 실행되는게 보입니다. 하지만, student 객체 생성시 student 객체에는 string 객체 두개가 멤버로 들어 있기 때문에, cout으로 명시적으로 보여준 생성자 , 소멸자 생성, 소멸의 과정 뿐만 아니라, student 객체가 생성될때마다 이들 string 객체 두개도 덩달아 생성되고 소멸이 되고, 이때마다 생성자, 소멸자를 호출 해야 하므로, 고비용의 문제가 발생하게 됩니다. 

그래서 이런 고비용 문제를 피하기 위한 방법이 있습니다. 바로 "상수 객체에 대한 참조자"로 전달하는 방법입니다. (Reference to const바로 Call by Reference 방법에 상수화를 더한 것이죠. 

방법은 bool validatestudent부분에서 괄호 부분을 (const student &stu); 이렇게 바꾸면 됩니다.이 방식의 좋은점은 새로 만들어지는 객체 같은 것이 없기 때문에, 생성자와 소멸자 호출도 당연히 없습니다.(Call by Reference의 장점과 같죠) 이로서 고비용의 부담문제를 해결 할수 있습니다. 

또한 여기서 주목해야 할 점은 const 상수 선언인데, 그 전에  call by value와 call by referecne가 가진 특징을 다시 한번 상기 시켜 봅시다. (제가 이미 포스팅한  링크 참조)


 우리가 사용하려는 참조자에 의한 전달 방법으로 고비용 문제는 해결했지만, 원본값이 변경될 우려가 있으므로, const를 씀으로서 이런 변화로부터 안전하게 보호를 해주는 것입니다. 

두번째 문제는 바로 복사손실 (slicing problem) 문제 입니다. Call by Reference를 씀으로 인해서 바로 복사손실을 방지할 수 있는 장점이 있는데요. 우선 예제 코드를 보겠습니다. 예제 코드는 윈도우 클래스로 만들어지는 객체는 이름을 갖고 있고, 이 이름은 name 멤버 함수로 얻어내고,  display 함수로 이를 화면표시를 해주는 예제 인데요. 
  1. #include <iostream>   
  2. #include <string>   
  3. using namespace std;   
  4.   
  5. class window   
  6. {   
  7. public:   
  8.     window()   
  9.     {   
  10.         cout<<"window class construcgtor call "<<endl;   
  11.         window_name = "window is shit";   
  12.     }   
  13.     string name() const  
  14.     {   
  15.         cout<<"window::name() call "<<endl;   
  16.         return window_name;   
  17.     }   
  18.     virtual void display() const  
  19.     {   
  20.         cout<<"-------------------------"<<endl;   
  21.         cout<<"window::display() call "<<endl;   
  22.         cout<<"-------------------------"<<endl;   
  23.   
  24.     }   
  25.     ~window()   
  26.     {   
  27.         cout<<"~window() call" <<endl;   
  28.     }   
  29. private:   
  30.     string window_name;   
  31. };   
  32.   
  33. class wnidowwithscrollbar :public window   
  34. {   
  35. public:   
  36.     wnidowwithscrollbar()   
  37.     {   
  38.         cout<<"wnidowwithscrollbar() call"<<endl;   
  39.     }   
  40.     void display() const  
  41.     {   
  42.         cout<< "------------------------"<<endl;   
  43.         cout<<"wnidowwithscrollbar :: display()"<<endl;   
  44.         cout<<"------------------------"<<endl;   
  45.   
  46.     }   
  47.     ~wnidowwithscrollbar ()   
  48.     {   
  49.         cout<<"~wnidowwithscrollbar() call "<<endl;   
  50.     }   
  51. };   
  52.   
  53. void print_name_and_diplay(window w) //윈도우의 이름을 출력하고 그 윈도우를 화면에 표시 하는 예제 함수입니다.    
  54. {   
  55.     cout<<"------ print name and display ---------"<<endl;   
  56.     cout<<w.name()<<endl;   
  57.     w.display();   
  58. }   
  59.   
  60. int main()   
  61. {   
  62.     wnidowwithscrollbar wsb;   
  63.     print_name_and_diplay(wsb);   
  64.   
  65.     return 0;   
  66. }  

 위 함수들을 파생클래스 객체를 넘긴다면 , 매개변수 w가 생성되기는 하지만, 어떤 타입의 객체가 넘어가든 window 클래스 객체의 면모만을 갖게 되므로, 가상함수를 사용했음에도 불구하고 windowwithscroolbars::display는 출력되지 않는 것이 바로 문제가 됩니다. 여기도 마찬가지로 문제를 해결하기 위해서는 상수 객체에 대한 참조자로 전달 하도록 만들면 됩니다. 
  1. void print_name_and_diplay(const Window &w)  
이렇게 만들면 어떠한 객체 타입이 넘어와도 그 고유 성질을 그대로 갖게 되므로 복사손실은 발생하지 않습니다. 

항목 20의 제목에서 나타난거와 같이 상수객체 참조자에 의한 전달방식이 택하는 편이 고비용, 복사손실 두개의 문제해결능력으로 봐서는 대게는 나은 결과를 보여주지만, 기본제공 타입, STL반복자 그리고 함수 객체 타입, 이 세가지는 이전부터 값으로 전달되도록 설계해 왔기 때문에 이 두가지 경우에는 pass by Value 방법을 사용하는 것이 더 적절합니다. 

 ※ 상황에 따라 적절하게 Call by Value , Call by Reference를 쓰자~!!!
Posted by 모과이IT
,
형변환 (Typecasting)  
 : 그동안 명시적인 형변환을 할때는 괄호를 사용했었는데 C++에서는 이를 대체 할 수 있는 4가지 종류의 형변환 연산자가 추가 되었다. 그럼 그동안에 사용했던 명시적 형변환을 계속 쓰지 않고 새로운 형변환 종류가 추가 되었을까? 일단 기존의 C 스타일 형변환은 두가지 문제점이 있다. 첫번째는 C 스타일의 형변환(컴파일 타입 형변환)은 눈에 잘 띄지도 않고 찾아내기 힘이 든다는 점이다. 사용자가 프로그램을 짜다 보면, 형변환 말고도 괄호를 사용하는 부분이 많기 때문이다. 뭐 눈에 잘 안띈다고 단점이될까? 
  명시적 형변환을 수행한다는 것은 암시적인 형변환이 불가능하다는 뜻이고, 암시적인 형변환이 불가능하다는 것은 컴퓨터가 생각하기에는 문제의 소지가 있다는 뜻이다. 다음으로 C 스타일의 형변환은 형변환의 의도를 구별해내기가 힘들다는 문제점도 있다. C++에서의 형변환 연산자는 그 용도에 따라서, 안전한 형변환, const 속성을 제거하는 형변환, 위험한 형변환, 클래스 타입간의 형변환 등으로 나뉘어져 있다. 그렇기 때문에 C++ 연산자를 사용해서 형변환을 하면 코드를 읽는 사람이 형변환의 의도를 쉽게 알아챌 수 있다. 컴퓨터 역시 코드를 작성한 사람의  의도를 파악 할 수 있기 때문에 컴퓨터가 개발자의 실수를 발견해서 경고해주는 것도 가능하다. 

 const_cast  
 : const_cast는 어떤 타입에서 const 속성이나 volatile 속성을 제거 할때 사용한다. 
* volatile속성 
 : 변수를 정의할때 volatile 키워드를 붙여줄 수 있는데 컴퓨터는 가끔씩 어떤 이유에서 변수를 상수로 만들어버리는 작업을 하게 되는데,  volatile로 지정한 변수는 그 작업에서 열외가 된다. 대부분의 경우 변수를 상수로 만드는 작업은 프로그램의 성능을 높이는데 도움이 되지만, 어떤 상황에서는 문제를 일으키기 때문이다. (지금은 쓸데가 없지만, volatile은 Multi threading과 관련된 곳에서 사용할 일이 생길 수 있다.) 다음은 const int 타입을 int 타입으로 형변환 하는 코드이다. 
  1. const int value = 100;   
  2. int i = const_cast<int> (value);  

 reinterpret_cast  
 : reinterpret_cast는 일반적으로 허용하지 않는 위험한 형변환을(무조건적인 형변환 ) 할때 사용한다. 즉, 그 안의 데이터가 어떤 객체이던 그저 비트열로만 보고 원하는 형으로 강제로 변환을 한다는 것이다. 예를 들어 포인터를 정수로 변환하는 작업 등이 이에 해당 되겠다.
  1. int a, b;   
  2. a = reinterpret_cast<int>(&b);  

 static_cast  
 : static_cast는 가장 일반적인 형태의 형변환을 할때 사용한다. 만약에 A타입에서 B타입으로의 암시적인 형변환이 가능하다면 static_cast를 사용해서 B타입에서 A타입으로 형변환 할 수 있다. 예를 들어 double 타입을 char 형으로 형변환하는데 사용할 수 있겠다.
  1. double d = 10.0;   
  2. char c;   
  3. c = static_cast<char>(d);  
 static_cast는 명시적인 형변환이기는 하지만 대체적으로 안전한 형변환이라고 볼 수 있다. 아마도 우리가 수행하는 대부분의 형변환은 여기에 속할 것이다. 

 dynamic_cast  
 : 유일하게 C 스타일의 형변환으로는 흉내낼 수 없는 것이 dynamic_cast이다. dynamic_cast는 서로 상속 관계에 있는 클래스간에 형변환을 할 때 사용한다. 더불어 형변환을 수행하는 동시에 이 형변환이 안전한 것인지까지 검사 해준다. 그래서 dynamic_cast는 실시간에 형검사를 하거나 형변환할 때 사용한다.
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. class Parent   
  5. {   
  6. public:   
  7.     virtual void Print( ) {}   
  8. };   
  9. class Child1 : public Parent   
  10. {   
  11. public:   
  12.     void Print( ) { cout << "class Child1 no problem" << endl; }   
  13. };   
  14. class Child2 : public Parent   
  15. {   
  16. public:   
  17.     void Print( ) { cout << "class Child2 no problem" << endl; }   
  18. };   
  19. void main( )   
  20. {   
  21.     Parent *p = new Child1;   
  22.     Parent *p1 = new Parent;   
  23.   
  24.     Child2* faile_child = dynamic_cast<Child2*> (p1);   
  25.     if( NULL == faile_child )   
  26.         cout << "Child2 Null 반환." <<endl;   
  27.     else  
  28.         faile_child->Print();   
  29.   
  30.     Child1 *pChild1 = dynamic_cast<Child1*>(p);    
  31.     if( NULL == pChild1 )   
  32.         cout << "Child1 Null 반환." <<endl;   
  33.     else  
  34.         pChild1->Print();   
  35.   
  36.     Child2 *pChild2 = dynamic_cast<Child2*>(p);   
  37.     if( NULL == pChild2 )   
  38.         cout << "Child2 Null 반환." <<endl;   
  39.     else  
  40.         pChild2->Print();   
  41.   
  42.     try  
  43.     {   
  44.         Child2& C2 = dynamic_cast<Child2&> (*p);   
  45.     }   
  46.     catch (bad_cast& e)   
  47.     {   
  48.         cout<<"bad_cast currupt"<<endl;   
  49.     }   
  50. }  

 위 예제에서 본것처럼, dynamic_cast는 다운 캐스트, 즉 부모 클래스 타입에서 자식 클래스 타입으로 형변환 할때 유용하게 사용할 수 있다. 다운 캐스트는 포인터나 레퍼런스가 가리키고 있는 객체의 실제 타입이 무엇이냐에 따라서 안전할 수 있고 위험할 수도 있는데 dynamic_cast가 알아서 안전여부는 검사를 해준다. 
 만약에 형변환에 문제가 있는 경우라면 dynamic_cast 연산자는 NULL 값을 반환하거나 bad_cast 예외를 던지게 된다.(위 결과 처럼 말이다.) 포인터의 형변환이라면 NULL을 반환함으로써 문제 상황을 알릴 수 있지만, 레퍼런스의 형변환인 경우에는 어떤 특정한 값은 반환하는 것이 불가능하므로 bad_cast 예외를 던지게 된다. (bas_cast 에외 역시 C++의 다른 예외 클래스들처럼 exception 클래스를 상속받았다.)
 위에서는 의미도 없는 가상함수를 사용했는데, 가상함수가 하나도 없는 클래스는 dyynamic_cast를 사용할 수 없기 때문이다. 이는 RTTI의 내부 구현과 관련이 있다. 위의 예제는 나머지 자식 클래스들도 상속받아 물려받은 가상 함수가 있는 셈이므로 dynamic_cast를 사용 할 수 있는 것이다. 만약 클래스가 가상함수를 하나도 가지지 않는다면 해당 클래스는 타입으로는 RTTI 를 이용할 수 있지만 객체로는 RTTI 를 이용할 수 없다.

 그럼 검사도 해주고 형변환도 해주는 dynamic_cast가 만능이냐? 아니다. static_cast<Child1*>(p); 이와 같이 static_cast를 해줄 수도있다. 하지만 이는 아무런 검사도 하지 않고 형변환을 하기 때문에, 실제론 잘못동작할수 있는 코드가 아무런 경고나 에러없이 컴파일되게 되는 불안요소를 가지고 있다고 할 수 있죠. 
 dynamic_cast는 RTTI를 이용해서 런타임시에 형을 체크하기 때문에, 잦은 dynamic_cast는 눈에 띌 정도의 퍼포먼스 저하의 원인이 될수 있습니다. 그렇기 때문에 자신이 하는 형변환하는 것이 안전하고 확실하다고 생각할때만 static_cast를 이용하고, 확신할수 없을 경우는 dynamic_cast를 이용하여 널포인터를 체크하는 것이 바람직하다.

 RTTI (Runtime Type Information)  
 : RTTI (Runtime Type Information)실행시간에 객체의 타입에 대한 정보를 얻을 수 있는 기능을 말한다. C++은 클래스의 객체만 가지고선 어떤 클래스의 객체인지 알수 있는 방법이 원래 없기 때문이다. 형변환 중 dynamic_cast를 할려면 RTTI가 필요 한데, 우리가 많이 쓰는 Visual Studio는 기본적으로 RTTI 기능을 사용하지 않게 설정되어 있다. 왜냐하면 RTTI의 특성상 객체를 생성할때마다 그 객체 내부에 타입 정보와 상속 정보를 넣어두기 때문에 속도(퍼포먼스)의 저하가 일어나기 때문이다. 
그래서 RTTI 기능 및 dynamic_cast를 사용하기 위해서는 비주얼 스튜디오의 프로젝트 설정을 변경 시켜 줘야 한다. 프로젝트의 Properties에 들어가 C++ -> Language -> Enable Run-Tie Type Information을 Yes(/GR)로 바꿔주면 된다.

  그럼 이 RTTI와 dynamic_cast는 어떻게 작동을 하는 것일까? 컴파일러는 RTTI 와 객체를 연결하기 위해서 가상함수 포인터 테이블을 이용을 한다. 원래 C++ 언어의 가상함수 포인터 테이블은 순수한 가상함수에 대한 함수 포인터 배열이다. RTTI 와 객체의 연결을 위해 C++ 언어는 가상함수 포인터 테이블 앞에 4 byte 를 만들고 이것을 RTTI 와의 연결 고리로 사용한다. 
 프로그램이 dynamic_cast 를 이용하여 캐스트를 한 경우 실행 코드는 dynamic_cast 의 표현식에 기술된 객체를 이용하여 RTTI 포인터 테이블을 검색하고, 만약 RTTI 포인터 테이블 상에 일치하는 RTTI 가 존재 한다면 표현식에 기술된 객체의 타입을 변환하여 반환하고, RTTI 포인터 테이블 상에 일치하는 RTTI 가 존재 하지 않는다면 dynamic_cast 는 NULL(0) 을 반환을 할 것이다. 
Posted by 모과이IT
,
예외 처리  
 : 예외란 무엇인가? 예외란 일반적이지 않는 상황을 말하는 것이다. 에러가 아니라는 말이다. 이런 일반적이지 않은 프로그램의 흐름을 처리 하는것을 예외 처리 라고 한다. 

 Try, Catch, Throw  
 : 일단 예외를 처리 하려면, 그에 맞는 문법을 써야 할 것이다. 
  1. try  
  2. {    
  3.     //예외 발생 예상지역   
  4. }   
  5.   
  6. catch(처리되어야할 예외의 종류)   
  7. {   
  8.     //예외를 처리하는 코드가 존재할 위치   
  9. }  
 위는 try, catch 문이다. try에는 예외라 불릴 상황을 가진 문장을 집어 넣는 것이다. (try 문은 무조건 실행이 된다.) 처리되어야 할 예외의 종류를 받아 catch 문에서는 이 예외에 대한 처리를 해주는 것이다. (명시적으로 cout문을 이용해 예외가 발생했다고 처리해 주거나 한다)
  1. if ( 조건 )   
  2.     throw ex;  
 다음은 throw로 위와 같이, "조건에 합당하면 예외를 던진다" 예제 소스코드를 보면 이해가 더 쉬울 것이다.
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. int main(void)   
  5. {   
  6.     int num;   
  7.   
  8.     cout<<"input number: ";   
  9.     cin>>num;   
  10.   
  11.     try{   
  12.         if(num>0)   
  13.             throw 10;  // int형 예외 전달.   
  14.         else  
  15.             throw 'm';  // char형 예외 전달.    
  16.     }   
  17.     catch(int exp){   
  18.         cout<<"int형 예외 발생"<<endl;   
  19.     }   
  20.     catch(char exp){   
  21.         cout<<"char형 예외 발생"<<endl;   
  22.     }   
  23.     return 0;   
  24. }  

 입력을 받는다고 가정하자. 이 입력받는 곳에는 숫자만 들어와야 하는데, 만약 문자가 들어 온다면 이 상황에서 문자가 들어온 것이 바로 예외 상황이다. 이런 예외 처리를 만약 문자형을 받으면 "예외를 던져(throw exception)" 그 예외에 해당하는 상황을 출력하는 간단한 예제이다. 여기에서 예외 전달을 위해 int 타입도 예외를 전달하는 모습을 보여주고 있다.
Posted by 모과이IT
,
템플릿의 동작원리는 함수 오버로딩(Function Overloading)과 유사한 형태로 구성이 된다. 다음 템플릿화된 소스코드를 보자.
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. template <typename T>   
  5. T Add(T a, T b)   
  6. {   
  7.     return a+b;   
  8. }   
  9.   
  10. void main()   
  11. {   
  12.     cout << Add(10,20);   
  13. }  
 실제 위의 소스 코드는 어떻게 동작을 할까? 실제 main 함수에서 Add(10,20); 이 문장에서는 실제 아래와 같은 함수가 만들어진다.
  1. int Add(int a, int b)   
  2. {   
  3.     return a+b;   
  4. }  
 그래서 우리가 컴파일 하고 실행할 때 실제로 호출 하는 함수는 템플릿 기반으로 만들어진 int Add 함수를 컴파일러가 자동적으로 생성하고 호출 하는 것이다. 우리가 지금까지 컴파일러가 코드를 자동적으로 생성해 주는 경우는 없었지만, 컴파일 타임에 이런 인스턴스화가 일어난다. 이렇게 함수 템플릿이 인스턴스화 되어 나온것이 바로 템플릿 함수이다. 함수 템플릿템플릿 함수 이런 차이점이 있다.
 클래스 템플릿도 마찬가지이다. 하지만 클래스 템플릿을 만들 때, 주의할 점이 하나있다. 일반적으로 우리는 클래스를 만들때, (지금까지 예제는 main 함수가 있는 cpp 한곳에 만들었지만..) 헤더(header) 파일과, cpp 파일로 분리 해서, 헤더에는 선언만 cpp에는 정의만 이렇게 나눠서 작성을 할 것이다. 
 앞서 말했듯이, 함수 템플릿이 인스턴스화 되어 템플릿 함수가 실제로 호출이 일어나 프로그램을 실행하는 것이다. 템플릿 함수는 컴파일러가 만드는데 이렇게 파일을 나눠서 구현을 해버리고, main 함수에서 실제 호출 하려고 하면, 에러가 나는 것을 알 수 있다. 
 cpp에서 정의 부분을 넣는데, cpp의 정의 부분은 링커(Linker)가 호출해주므로 이런 문제가 발생하는것이다. 그래서 만약 우리가 클래스 템플릿을 만들어서 쓰고 싶다면 헤더에 선언과 정의를 한번에 다 하는 것이 좋다.
Posted by 모과이IT
,
함수 템플릿  
 템플릿의 종류에는 함수 템플릿, 클래스 템플릿 이 두가지로 나뉠 수 있다. 책마다 함수 템플릿 또는  템플릿 함수로 이 두가지로 표기가 되어 있는데, 이런 명사 두개가 오는 단어들은 뒤에가 진짜다. 우선 함수 템플릿, 템플릿 함수의 의미를 정리 해보자.
 - 함수 템플릿 : 함수를 기반으로 구현이 된 템플릿 (함수가 아니라는 뜻이다)
 - 템플릿 함수 : 템플릿을 기반으로 한 함수라는 뜻
 우리가 앞서 본 템플릿 예제는 함수 템플릿이다. 이 함수 템플릿에 대해 좀 더 자세히 알아 보자.

 둘이상의 타입에 대한 템플릿  
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. template <typename T> // 함수 템플릿 정의   
  5. void ShowData(T a, T b)    
  6. {   
  7.     cout<<a<<endl;   
  8.     cout<<b<<endl;     
  9. }   
  10.   
  11. int main(void)   
  12. {   
  13.     ShowData(1, 2);   
  14.     ShowData(3, 2.5); //error   
  15.   
  16.     return 0;   
  17. }  
 위와 같이 showdata의 인자값의 데이터 자료형을 각각 달리 해주면, 에러가 나오는 것을 알 수 있다. 이 경우 아래와 같이 사용하면 된다.
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. template <typename T1,typename T2> // 함수 템플릿 정의   
  5. void ShowData(T1 a, T2 b)    
  6. {   
  7.     cout<<a<< " ";   
  8.     cout<<b<<endl;     
  9. }   
  10.   
  11. int main(void)   
  12. {   
  13.     ShowData(1, 2);   
  14.     ShowData(3, 2.5);   
  15.   
  16.     return 0;   
  17. }  


 템플릿 특수화  
 : 특수화란 범위를 좁히는 것을 말한다. 다음과 같은 경우가 있다고 한번 가정해 보자. 
  1. #include <iostream>   
  2. using std::endl;   
  3. using std::cout;   
  4.   
  5. template <typename T> // 함수 템플릿 정의   
  6. int SizeOf(T a)    
  7. {   
  8.     return sizeof(a);   
  9. }   
  10.   
  11. int main(void)   
  12. {   
  13.     int i=10;   
  14.     double e=7.7;   
  15.     char* str="Good morning!";   
  16.   
  17.     cout<<SizeOf(i)<<endl;   
  18.     cout<<SizeOf(e)<<endl;   
  19.     cout<<SizeOf(str)<<endl;   
  20.   
  21.     return 0;   
  22. }  

 원래 있던 sizeof 함수를 템플릿화를 시켰다. 뭐 이상없이 찍힌다. 캐릭터형 포인터는 4가 찍히는 것이 맞겠지만, 사용자는 그 데이터형의 사이즈는 관심없고, 문자열이 들어 있다면 그 문자열의 크기가 얼마 인지 알고 싶다고 해보자. 하지만 위와 같이 템플릿화한 경우에는 변수 사이즈만 찍힐 것이다. 그래서 캐릭터형 포인터의 데이터 타입을 받으면 그 경우에는 문자열 크기를 알아 보는 strlen 함수를 써서 문자열 길이를 반환하는 것. 즉, 어떠한 경우에서만 특별히 다른 행동을 했으면 좋겠다. 이것이 바로 특수화다. 문법 사용은 다음과 같이 쓴다.
  1. #include <iostream>   
  2. using std::endl;   
  3. using std::cout;   
  4.   
  5. template <typename T> // 함수 템플릿 정의   
  6. int SizeOf(T a)    
  7. {   
  8.     return sizeof(a);   
  9. }   
  10.   
  11. template<> // 특수화   
  12. int SizeOf(char* a)    
  13. {   
  14.     return strlen(a);   
  15. }   
  16.   
  17. int main(void)   
  18. {   
  19.     int i=10;   
  20.     double e=7.7;   
  21.     char* str="Good morning!";   
  22.   
  23.     cout<<SizeOf(i)<<endl;   
  24.     cout<<SizeOf(e)<<endl;   
  25.     cout<<SizeOf(str)<<endl;   
  26.   
  27.     return 0;   
  28. }  

 클래스 템플릿  
 : 클래스를 템플릿 하고자 하면, 원하는 자료형만 T로 바꾸면 된다. 아래의 클래스를 예로 들어 보자. 
  1. class Data   
  2. {   
  3.     int data;   
  4. public:   
  5.     Data(int d){   
  6.         data=d;   
  7.     }   
  8.     void SetData(int d){   
  9.         data=d;   
  10.     }   
  11.     int GetData(){   
  12.         return data;   
  13.     }   
  14. };  
 템플릿화~
  1.   
  2. template <typename T>   
  3. class Data   
  4. {   
  5.     T data;   
  6. public:   
  7.     Data(T d){   
  8.         data=d;   
  9.     }   
  10.     void SetData(T d){   
  11.         data=d;   
  12.     }   
  13.     T GetData(){   
  14.         return data;   
  15.     }   
  16. };  
 그럼 이렇게 선언한 클래스 템플릿을 main에서는 어떻게 사용할까? 이전처럼 사용하면 되는 것일까? Data d1(10); 이렇게 사용하면 문제가 된다. 무엇이 문제가 되는지 한번 자세히 알아 보자.
 우선 객체 생성 순서에 대해 생각해 보자. 메모리 할당 -> 생성자 호출.... 이런식이다. 그러면 Data d1(10); 이 문장에서  d1 이라는 이름으로 메모리 공간을 할당을 해야 할 것이다. 하지만 우리는 클래스 템플릿을 사용하고 있기 때문에,T가 어떤 데이터형을 사용해야 할건지가 결정이 나야 메모리 할당이 이루어는 구조를 가지고 있다. T가 결정나는 시점은 생성자가 호출되어야만 (괄호 안의 10이 호출 되어야만..) T가 int형 데이터 인지 알 수 있으므로 이전과 같은 문법을 사용한다면 메모리 할당을 전혀 하지 못하게 되는 것이다. 
 그래서 템플릿 클래스에서 Data 객체를 만들기 위해서는 생성자를 통해서 전달되는 인자의 정보를 참조하는 시기가 늦기 때문에 구체적으로 어떤 타입으로 템플릿을 구체화 시킬지 명시적으로 선언을 해줘야 한다. 아래의 실사용 예제를 보자.
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. template <typename T> //Data 라는 템플릿 Data<T>가 이것의 이름이 된다.   
  5. class Data   
  6. {   
  7.     T data;   
  8. public:   
  9.     Data(T d);   
  10.     void SetData(T d);   
  11.     T GetData();   
  12. };   
  13.   
  14. template <typename T>   
  15. Data<T>::Data(T d){   
  16.     data=d;   
  17. }   
  18.   
  19. template <typename T>   
  20. void Data<T>::SetData(T d){   
  21.     data=d;   
  22. }   
  23.   
  24. template <typename T>   
  25. T Data<T>::GetData(){   
  26.     return data;   
  27. }   
  28.   
  29.   
  30. int main(void)   
  31. {   
  32.     Data<int> d1(0);   
  33.     d1.SetData(10);   
  34.   
  35.     Data<char> d2('a');   
  36.   
  37.     cout<<d1.GetData()<<endl;   
  38.     cout<<d2.GetData()<<endl;   
  39.   
  40.     return 0;   
  41. }  

Posted by 모과이IT
,
템플릿?  
 : Template 이라는 단어는 모형자라는 의미를 가진단어로, C++에서 템플릿은 어떤 제품을 만들어내는 틀, 예를 들어 붕어빵에 비교해 보자면, 붕어빵을 만들어 내는 틀을 템플릿이라 말 할 수 있다. 템플릿의 특징은 기능은 이미 결정되어 있지만, 데이터 타입은 결정되어 있지 않는다는 특징을 가지고 있다. 아래에는 Sub라는 함수가 있다.
  1. int Sub(int a, int b)   
  2. {   
  3.     return a-b;   
  4. }  
 Sub라는 함수는 두개의 int 형 데이터를 서로 빼주는 그런 함수이다. 이런 함수를 한번 템플릿화 해보겠는데, 템플릿의 특징은 무엇이라 했는가? 바로 기능은 결정되어 있고 데이터 타입은 결정되지 않는 그런것이라고 언급을 했었다. 여기서 기능은 Sub(바로 빼주는) 거라고 할 수 있고, 데이터 타입은 int 이다. 이것은 유념해 두고 템플릿화 해보면 다음과 같이 템플릿화 할 수 잇다. 
  1. template <typename T>   
  2. T Sub(T a, T b)    
  3. {   
  4.     return a-b;   
  5. }  
 여기서 template <typename T>는 T라는 타입이름에 대해서 그 아래 존재하는 함수를 템플릿화 하겠다는 의미이다. 여기서 T라는 자료형은 Sub라는 이 함수를 사용할 때, 결정된다. 그럼 이 템플릿화 한 함수를 한번 직접 써보자.
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. template <typename T1, typename T2> // 함수 템플릿 정의   
  5. void ShowData(T1 a, T2 b)    
  6. {   
  7.     cout<<a << " ";   
  8.     cout<<b<<endl;     
  9. }   
  10.   
  11. int main(void)   
  12. {   
  13.     ShowData(1, 2);   
  14.     ShowData(3, 2.5);   
  15.   
  16.     return 0;   
  17. }  

이와 같이 템플릿(Template)은 자료형에 독립적으로 쓸 수 있다는 장점을 가지고 있다. 
Posted by 모과이IT
,
대입 연산자  
 위와 같은 문장이 있다. 여기서 b는 A클래스의 b객체이다. 이 문장은 다음과 같이 묵시적으로 변환이 일어난다. A a(b); 결국 a라는 이름으로 객체 생성할때 복사 생성자를 호출 하는 형태가 된다. 그럼 다음과 같은 문장은 어떤 일이 일어날까?
  1. A a;   
  2. B b;   
  3. a = b;  
 여기서 a = b; 이 문장은 a(b) 이런 형태로 묵시적 변환이 일어 나지 않는다. 위와 같은 대입연산을 썼지만, 객체 생성문장이 아니기 때문이다. 그럼 여기서 a = b;는 다음과 같이 해석 될 것이다. a.operator=(b); 복사생성에서 호출되는 대입연산과 오버로딩 되어 있는 operator 함수 호출에서의 대입연산을 혼동하지 말자

 대입 연산자 오버로딩  
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. class Point {   
  5. private:   
  6.     int x, y;   
  7. public:   
  8.     Point(int _x=0, int _y=0):x(_x), y(_y){}   
  9.     friend ostream& operator<<(ostream& os, const Point& p);   
  10.     Point& operator = (const Point &p)   
  11.     {   
  12.         x = p.x;   
  13.         y = p.y;   
  14.         return *this;   
  15.     }   
  16. };   
  17.   
  18. ostream& operator<<(ostream& os, const Point& p)   
  19. {   
  20.     os<<"["<<p.x<<", "<<p.y<<"]";    
  21.     return os;   
  22. }   
  23.   
  24. void main()   
  25. {   
  26.     Point p1(1, 3);   
  27.     Point p2(10, 30);   
  28.     cout<<p1<<endl;   
  29.     cout<<p2<<endl;   
  30.   
  31.     p1=p2;    
  32.     cout<<p1<<endl;   
  33. }  

  위에서 말한것과 같이, 31번째 라인에서는 a.operator=(b); 이런 형태로 대입연산 오버로딩이 일어날 것입니다. 하지만 여기 소스코드에서는 어디에서도 대입 연산을 정의 하는 함수를 찾아 볼 수 없다. 그것은 바로 대엽 연산자 오버로딩에 대해서만 "디폴트 대입 연산자"가 존재하기 때문이다. 그래서 우리가 명시적으로 써주지 않아도 이 디폴트 대입 연산자를 호출해 문제가 발생하지 않는 것이다. 
 만약 우리가 정의 한다고 하면 아래와 같이 정의 할 수 있겠다. 
  1. Point& operator = (const Point & p);   
  2. {   
  3.     x = p.x; y = p.y;   
  4.     return *this;   
  5. }  
 왜 참조형으로 리턴을 하냐면, 대입 연산도 p1 = p2 = p3; 이런 형태의 대입연산도 쓰기 때문에, 이것을 감안해서 참조형으로 리턴을 해줘야 한다. 그럼 이렇게 디폴트 대입 연산자가 있는데 굳이 우리가 정의를 해서 쓸 일이 있을까? 반드시 정의를 해서 써야 하는 경우가 있는데, 그 경우가 바로 깊은 복사(Deep Copy)가 요구 되어 지는 경우이다. 이와 같이 디폴트 대입 연산자의 문제점을 알아 볼 수 있다. 

 문제점  
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. class Person {   
  5. private:   
  6.     char* name;   
  7. public:   
  8.     Person(char* _name);   
  9.     Person(const Person& p);   
  10.     ~Person();   
  11.     friend ostream& operator<<(ostream& os, const Person& p);   
  12. };   
  13.   
  14. Person::Person(char* _name){   
  15.     name= new char[strlen(_name)+1];   
  16.     strcpy(name, _name);   
  17. }   
  18. Person::Person(const Person& p){   
  19.     name= new char[strlen(p.name)+1];   
  20.     strcpy(name, p.name);   
  21. }   
  22. Person::~Person(){   
  23.     delete[] name;   
  24. }   
  25.   
  26. ostream& operator<<(ostream& os, const Person& p)   
  27. {   
  28.     os<<p.name;   
  29.     return os;   
  30. }   
  31.   
  32. int main()   
  33. {   
  34.     Person p1("One");   
  35.     Person p2("Two");      
  36.   
  37.     cout<<p1<<endl;   
  38.     cout<<p2<<endl;   
  39.   
  40.     p1=p2;  //error   
  41.   
  42.     cout<<p1<<endl;   
  43.   
  44.     return 0;   
  45. }  
 위의 소스 코드를 실행해 보면 컴파일에는 문제가 없지만, 런타임에서 문제가 생긴다. 그럼 이런 문제는 왜 일어 나는 것일까?
 여기서는 Person 클래스의 p1과 p2 객체를 생성하는데 p1과 p2는 객채 내에 name이라는 포인터가 있어서 문자열의 길이 만큼 동적 할당을하고 이 저장한 공간을  p1 객체가 포인터를 유지하는 형태를 띠게 되는데, 동적할당은 Heap 영역에 p1과 p2객체는 스택에 생성이 된다.
 그 후, 40번째 라인의 p1 = p2; 은 p1.operator=(p2) 이것으로 해석 되므로, 대입 연산자 오버로딩이 일어 날 것이다. 우선 여기서는 대입 연산자가 따로 정의 되어 있지 않으므로, 디폴트 대입 연산자를 사용해서 p2가 인자로 전달되어 멤버간의 복사가 일어 날 것이다. (p2가 지니는 값을 p1에 복사하는 것이다.) 하지만 p2의 멤버는 객체의 포인터이다. 그래서 객체의 포인터 값을 p1에다 복사 할텐데, 처음 가리키고던 "One"이 끊어지고 p1은 "Two"를 가리키게 된다. 이게 바로 문제다. 
 이 문제는 복사 생성자에서도 생겼던 문제 인데, 하나의 문자열을 두개의 객체가 참조 하다 보니까 소멸자 호출에 있어서 문제가 생기고,(이것은 복사생성자에서도 생겼던 문제) 포인터를 잃어 버렸기 때문에 프로그램 종료될때까지 메모리 공간에서 메모리를 잡아 먹게 되어 메모리 유출이 발생하는 문제가 발생된다. 그래서 이 경우에는 대입 연산자를 꼭 재정의 해줘야 한다. 
 일단 포인터가 끊어지고 생기는 메모리 유출에 대한 문제를 해결하고, 메모리 공간을 다시 할당해 복사를 해주자. 아래는 새로 정의된 대입 연산자 함수와 결과 출력이다. 이제는 문제 없이 출력되는 것을 알 수 있다.
  1. Person& Person::operator=(const Person& p)   
  2.     {   
  3.         delete []name;   
  4.         name= new char[strlen(p.name)+1];    
  5.         strcpy(name, p.name);    
  6.         return *this;   
  7.     }  

Posted by 모과이IT
,
쉬프트 연산자 <<, >>  
 : 쉬프트 연산자란, 우리들이 흔히 쓰는 cout, cin, endl 키워드를 쓸때 앞이나 뒤에 붙이는 연산자이다. 일반적으로 표준 라이브러리에 std라는 namespace에는 cout이나 cin에 관련된 클래스들이 있다. 그것이 바로 istream과 ostream이다. 이런 namespace를 일일이 입력하기 싫어서 using namespace 라는 간편한 문법을 쓰기도 한다. C++에서는 이런 쉬프트 연산자도 연산자 오버로딩이 가능하다.

 쉬프트 연산자 오버로딩  
  1. int main(void)   
  2. {   
  3.     Point p(1, 3);   
  4.     cout<<p;   
  5.   
  6.     return 0;   
  7. }  
 메인함수를 보자. 어떤 Point 클래스가 있고, 이 Point 클래스를 p라는 객체를 통해 생성하고 있고, 이 객체를 cout을 이용해 출력하려는 문장이다. 하지만 기본적으로 cout은 기본자료형만 출력을 하지 객체인 사용자 타입의 자료형은 출력을 할 수가 없다. 4번째 줄을 멤버 함수로의 오버로딩 해석에 의해 풀어 써 보자. 
 cout.operator<< (p) 즉, p객체를 인자값을 받을 수 있도록 cout 객체에는 operator<< 연산자가 오버로딩 되어 있어야 하는데, 표준 std의 ostream 클래스에서도 쉬프트 연산에 대한 오버로딩이 정의 되어 있지 않다. 그래서 우리가 표준 라이브러리를 건드리면서까지 객체를 출력하는 일은 불가능 할 것이다. 
 이로서 멤버 함수로의 오버로딩으로는 객체를 출력할 수 없다는 것을 알 수 있었다. 그럼 전역 함수의 오버로딩을 보자.전역함수로의 오버로딩에서는 cout << p 문장은 operator (cout, p); 로 풀어 써지고, 이것은 논리적으로 문제가 없다. 그럼 전역함수로 오버로딩으로 다음과 같이 함수를 정의 할 수 있겠다. 
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. class Point {   
  5. private:   
  6.     int x, y;   
  7. public:   
  8.     Point(int _x=0, int _y=0):x(_x), y(_y){}   
  9.     friend ostream& operator<<(ostream& os, const Point& p);   
  10. };   
  11.   
  12. ostream& operator<<(ostream& os, const Point& p)   
  13. {   
  14.     os<<"["<<p.x<<", "<<p.y<<"]"<<endl;    
  15.     return os;   
  16. }   
  17.   
  18. int main(void)   
  19. {   
  20.     Point p(1, 3);   
  21.     cout<<p;   
  22.   
  23.     return 0;   
  24. }  

Posted by 모과이IT
,