Friday, August 30, 2013

[Translation] A Quick Introduction To C++


[Translation] A Quick Introduction To C++   ----  Tom Anderson
[번역] C++에 대한 간단 소개 -- 원 저자 톰 앤더슨

1. Introduction

이  튜토리얼의  기본   전제는,  비록  객체지향  프로그래밍  패러다임으
프로그램을 단순하게 만드는데  도움이 되지만 객체지향 프로그래밍 언어인
C++는  잘  쓰이지  않는   특징들을  너무나  많이  포함하고  있어  매우
복잡하다는 것이다.

따라서  이  튜토리얼에서는 첫째  기본적이고  반드시 사용해야할  특징들
클래스,   멤버함수,  생성자를  설명하고,   둘째  다른   사람이  작성한
프로그램에서  사용되었다면  이해할  수  있어야  하지만  자신은  가능한
사용하지  말아야 할  특징들 단일  상속, 템플릿을  설명하고,  세째 나쁜
특징들로써 반드시 피해야할 특징들 다중 상속, 예외상황, 중복, 참조 등을
설명한다.

물론  어떤 프로그래밍  언어의 특징이  좋은가 나쁜가  하는가에 대해서는
견해차가 사람들마다  매우 다를 수  있다. 예를 들어 다중  상속의 특징이
반드시  필요하다고 생각한다면 다중  상속을 사용한  프로그램과 사용하지
않은  프로그램을 모두  작성해본  다음 비교해  볼  것을 제안한다.  이런
시도들이 어떤 특징이 유용한지 아닌지를 쉽게 알게 될 것이다.

2. C in C++

기본적으로 C++는 C를 포함하도록 언어가 정의되어 ANSI C 프로그램은 C++
프로그램으로 대부분 컴파일 된다. 하지만 중요한 다른 점도 있으므로
몇가지 나열한다.

    1. 모든 함수는 사용되기 전에  선언되어야 한다.  All functions must
    be declared before  they are used, rather than  defaulting to type
    int. ???

    2. 모든 함수 선언과 정의 헤더 (definition headers)는 다음의 새로운
    스타일로 정의되어야 한다.

    extern int foo (int a, char* b);

    C++에서  extern int  foo(); 은  아무 인자를  받지 않는  함수 foo를
    뜻한다. 이것은  C에서 인자 수와  타입을 지정하지 않은  함수 선언과
    다른 의미이다.   혹자는 C 프로그램에 C++  컴파일러를 사용해서 잘못
    사용된 함수와 같은 에러를 잡아낼 수 있다고 한다. 대개 C 컴파일러는
    그러한 에러를 그냥 지나친다.

    3. C 함수를 C++ 프로그램에서 사용하려면 다음과 같이 선언해야 한다.

    extern "C" int foo (int a, char* b);

    왜냐하면 C++ 컴파일러는 프로그램에서 사용된 이름을 이상한 방법으로
    바꾸기 때문이다. (Name mangling)

    4. C++에서  키워드  new,  delete,  const,  class  등은  이름으로써
       사용하지 말아야 한다.

3. Basic Concepts

    1. 클래스와  객체: 클래스는  C의 struct와  유사하고  struct과 같은
       데이터구조에 대해 연산하는 모든 함수를 한 곳에 모아놓은 것이다.
       객체는   클래스의   instance로   (struct의  instance인   자료와
       유사하게)  객체는  같은 클래스의  다른  객체들과 동일한  함수를
       공유한다.   물론  각각의 객체는  자신의  값을 가진다.  클래스는
       이렇게  객체가  포함하는   데이터와  객체가  보이는  behavior를
       정의한다.

    2. 멤버  함수:  객체의 일부로  간주되는  함수이고 클래스  정의에서
       선언된다.   method라고   불리기도  한다.   클래스의  behavior를
       정의하는 멤버  함수와 더불어  객체를 새로 만들때  무엇을 할지를
       (즉,   객체의   데어터를   어떻게   초기화   할지를)   정의하는
       constructor와 객체를 없앨때 무엇을 할지를 정의하는 destructor도
       클래스이 behavior를 정의한다.

    3. private vs. public 멤버:  public 멤버는 누구나 읽고 쓰고 호출할
       수 있고,  private 멤버는 클래스의 멤버 함수만  읽고 쓰고 호출할
       수 있다.

클래스를 사용하는 두가지 이유는, 첫째 자료와 이 자료를 다루는 함수들을
함께 모아서  전체 프로그램의 구조를  구성하기 쉽게 하고,  둘째 private
멤버를 사용해서  information hiding 방법을  제공해서 프로그램에서 정보
흐름 방식을 더 확실히 제어할 수 있다.

3.1 Classes

C++  클래스는 C에서  struct와 유사하다.  사실 C++에서  struct는 public
데이터 멤버만 포함하는 클래스이다.

1. 멤버 함수

class Stack {
  public:
    void Push(int value);
    int top;
    int stack[10];
}

void
Stack::Push(int value) {
    ASSERT(top<10);
    stack[top++] = value;
}

이 클래스는  두 data  멤버 top, stack과  하나의 함수 멤버  Push를 갖고
있다.   class::function   표기법은  어떤  클래스   class의  멤버  함수
function을 지칭한다. 함수는 클래스 선언 아래에 정의한다.

여담으로  ASSERT의  사용에  대해서  언급한다.   위의  ASSERT는  스택의
오버플로우  되지 않았음을  검사한다. ASSERT를  통해  구현에서 가정했던
바를 분명히 소스 코드에 쓰는 것은 매우 좋은 습관이다.

일반적으로 class Stack의 정의는 stack.h 파일에 저장하고, Stack::Push와
같은 멤버 함수의 정의는 stack.cc 파일에 저장한다.
(c.f. stack.hpp stack.cpp)

Stack 클래스의 객체 s가 주어졌다면 s->top으로 해당 데이터 멤버 값을 읽을
수 있다. 또한 다음의 구문으로 멤버 함수를 호출할 수 있다.

s->Push(17);

멤버 함수 정의 내에서는  해당 클래스의 멤버들을 이름만으로 참조할 수도
있다.

멤버 함수 정의 내에서는 특별한 변수 이름 this를 통해 해당 멤버 함수가
호출된 객체에 대한 포인터를 얻을 수 있다.

위의 Stack 클래스에서 top을  멤버 함수 Full을 통해서 읽고 오버플로우를
검사하도록 바꿔보자.

class Stack {
  public:
    void Push (int value);
    bool Full();
    int top;
    int stack[10]
}

bool
Stack::Full() {
    return (top == 10);
}

void
Stack::Push(int value) {
    ASSERT(!Full());        
    stack[top++] = value;
}

위의  ASSERT를   ASSERT(!(this->Full()));와  같이  사용할   수  있지만
this->는 멤버 함수 정의 내에서는 생략할 수 있다.

멤버 함수의 목적은 객체에 포함된 자료와 함께 객체의 타입에 따라 정의된
기능들을 하나로  묶는데(encapsulate) 있다.  멤버  함수는 해당 클래스의
객체를 정의하는데 필요한 메모리 공간에 포함되는 것은 아니다.

2. private 멤버

자료 멤버와  함수 멤버는 public이나  private으로 선언될 수  있다. 위의
Stack  클래스에서 Full  멤버  함수를 public으로  선언하면 top과  stack
멤버는  굳이  외부에서 사용할  필요가  없어지므로  이 데이터  멤버들을
private으로 선언하자.

class Stack {
  public:
    void Push(int value);
    bool Full();
  private:
    int top;
    int stack[10];
};

이제는  s->Full()은  물론  가능하지만  s->top은 컴파일  에러를  초래할
것이다.

public:과  private:  섹션의  위치를  물론  바꿀 수  있다.  class  시작
부분에서  아무런  표시가 없다면  이  부분에  선언된 멤버들은  디폴트로
private으로 간주된다.

class Stack {
    int top;
    int stack[10];
  public:
    void Push(int value);
    bool Full();
};

추천하는 스타일은 모든 것을 명시적으로 선언하는 것이다.

모든  데이터  멤버들은  private으로  선언하고 필요하다면  public  멤버
함수를  통해서 데이터 멤버를  다루게 해야  한다. 이러한  방식은 데이터
멤버들을  내부적으로  재정의해도 외부에서  이를  다루는 부분은  바꾸지
않아도 되므로 전체 시스템을 더욱 모듈화하게 만든다.

3. 생성자와 new 연산자

C에서 Stack 타입의 객체를 새로 만드려면 다음과 같은 코드가 필요하다.

struct Stack *s = (struct Stack *)malloc(sizeof(struct Stack));
InitStack(s, 17);

C++에서는 다음과 같은 코드가 필요하다.

Stack *s = new Stack (17);

new 함수는 위의 C코드에서 malloc 역할을 한다. 스택을 어떻게 초기화
할지를 지정하려면 Stack 클래스이 멤버함수로서 constructor 함수를
선언한다. constructor 함수의 이름은 클래스의 이름고 동일해야 한다.

class Stack {
  public:
    Stack (int sz);
    void Push(int value);
    bool Full();
  private:
    int size;
    int top;
    int* stack;
};

Stack::Stack(int sz) {
  size = sz;
  top = 0;
  stack = new int[size];
}

new 연산자는 자동으로 객체를  새로 만들고 이 객체의 constructor 함수를
호출한다.  new 연산자를  사용하지 않더라도  함수나 블럭의  시작 부분에
자동  변수로 객체를 선언한  경우라도 (예를  들어 void  f() {  Stack v;
... } ) 동일한 순서로 객체가 만들어지고 constructor 함수가 호출된다.

예를 들어

void
test() {
    Stack s1(17);                // 스택을 자동변수로 선언해서 만드는 경우
    Stack* s2 = new Stack(23);   // 스택을 new를 통해서 만드는 경우
}

constructor  함수에 인자를  주는 방법으로  위의  예제에서처럼 두가지가
있다. new를 통해서 객체를 만드는 경우 클래스 이름 다음에 인자 리스트를
주고,  자동변수를 선언하여 객체를  만드는 경우  변수 이름  다음에 인자
리스트를 준다.

constructor  함수는  반드시  선언하고  해당 클래스의  데이터  멤버들을
초기화 하도록 코드를 작성하는  것이 바람직하다. 물론 construtor 함수를
정의하지  않으면  컴파일러가  자동으로  만들어주지만  컴파일러가  만든
constructor 함수는 프로그래머가 의도하는대로 constructor 함수를 만들어
주지는 못할 것이다.

함수에 선언된 C언어의 변수는 시작  시점에 새로 만들고 함수 리턴 시점에
없어지듯이 위의 예제에서  객체 s1을 함수 시작 시점에  만들고 함수 리턴
시점에 없앤다. 하지만 new연산자를  통해 만든 객체는 힙에 위치하고 함수
리턴 후에도 여전히 남는다.  delete 연산자를 통해서 직접 없애야 한다.
new 연산자는 배열을 만들때도 사용할 수 있다.

stack = new int[size];

new와 delete 연산자는 Stack 클래스의 객체에 대해서도 사용할 수 있을 뿐
아니라 int와 char와 같은 built-in 타입에 대해서도 사용할 수 있다.

4. destructor와 delete 연산자

C에서  malloc을 C++에서  new로  대신한 것  처럼  C에서 free를  C++에서
delete로  대신 사용한다. 예를  들어 위의  예제에서 만들었던  스택 객체
s2를 없애기 위해서 다음의 코드를 사용할 수 있다.

delete s2;

Stack 클래스의 destructor 멤버 함수는 ~Stack()이다.

class Stack {
  public:
    Stack(int sz);
    ~Stack();
    void Push(int value);
    bool Full();
  private:
    int size;
    int top;
    int* stack;
};

Stack::~Stack() {
    delete [] stack;
}

destructor 함수가 할 일은 주로 새로 만들어 놓았던 데이터를 없애는 일이다.
많은 경우 이 함수를 필요로 하지는 않는다. 어떤 경우는 오픈한 파일을 닫는
일을 하기도 하고, (... and otherwise clean up after themselves.???)
객체를 없애고자  할때 객체의  destructor 함수를 부른다.   new 연산자를
통해  만든 객체는 반드시  delete 연산자를  통해서 없애야  한다. 그렇지
않으면  객체를  만들기 위해  할당한  메모리를  해제하지 않아서  memory
leak이  발생할  수 있다.   또한  delete  연산자는  너무 일찍  호출하면
안된다.  delete  후에 그 객체를  다룰때 의미없는 자료를  읽거나 씀으로
해서 디버깅 하기 어려운 에러가 발생할 가능성이 매우 높다.

만일 위의 test() 함수  예제에서 처럼 객체를 자동변수로 선언했다면 함수
코드 실행 시작 전에  자동으로 (컴파일러가 추가한 코드에 의해) 만들었던
객체를 함수 리턴 직전에 (컴파일러가 추가한 코드에 의해) 없앤다.

저자는 new  연산자를 통해서만 객체를 만들 것을  추천한다. 왜냐하면 C++
언어에서 자동변수로 선언한 객체의 constructor 함수와 destructor 함수를
호출하는 방식이  직관적이지 않기  때문이다.  (???)  물론  new 연산자를
통해서 객체를  만드는 방식은 delete  연산자를 통해 객체를  없애야 하는
것을 잊어 버릴 가능성이 있기도 하는 문제점을 가지고 있다.

배열을 없애고자 할때는 배열의  원소가 아닌 배열 자체를 없애고자 한다는
뜻으로 Stack::~Stack()에서 처럼 []를 delete 연산자와 함께 사용한다.

delete [] stack;

3.2 Other Basic C++ Features

1. class Stack을 정의했다면 Stack 이라는 이름은 타입의 이름으로 사용될
   수   있다.  물론   enums으로   정의한  이름   역시  타입   이름으로
   사용가능하다.

2. 함수 정의를  class 안쪽으로  옮길 수도 있다.   이 경우  함수 정의는
   inline  함수로 간주되어  이  함수를 호출하는  곳에 컴파일러에  의해
   카피된다.  필요하다면 하나의 라인으로  된 함수의 경우만 inline 멤버
   함수로 정의하고 최대한 inline 멤버 함수는 피하는 것이 바람직하다.
   예를 들어 Full() 함수를 inline 멤버 함수로 정의할 수 있다.

   class Stack {
     ...
     bool Full() { return (top == size); }
     ...
   };

   inline 멤버  함수는 편리함과 성능  측면에서 고려할 수  있다. 하지만
   inline    멤버   함수를    빈번하게   사용하면    전체   프로그램이
   복잡해진다. 왜냐하면 객체를 구현하는  코드가 .h와 .cc 파일에 흩어질
   수 있다. 때로는  성능을 높일 수도 있지만 코드의  카피로 인해서 캐쉬
   성능에 영향을 미쳐 성능이 낮아질 가능성도 있다.

3. C++ 함수  정의에서느 C와 달리 코드의 어느  위치에서도 변수를 선언할
   수 있다. 이 방식은 프로그램을 쉽게 읽을 수 있게 한다. 또한 다음의
   코드도 가능하다.

   for (int i=0; i<10; i++);

   주의해야  할 점은  컴파일러에  따라 변수  i가  for루프 다음에  나올
   코드까지 스코프가 확장될 수도 있다.
  
4. 코멘트는 //와 /* */를 모두 사용할 수 있다.

5. ANSI C에서  유래한 const의 키워드를 C++에서  여러가지 새로운 용도로
   사용할  수 있다.  const를  사용하는 기본  아이디어는 변수나  함수가
   어떻게 사용될지를  컴파일러에게 추가로 알려서  변수나 함수가 선언된
   바와 다르게  부적절하게 사용되는  코드 부분이 있으면  컴파일 에러를
   나도록 하는 것이다.

   예를 들어서 멤버 함수가 멤버 데이터만을 읽기만 하고 객체를 변경하지
   않음을 선언하는 방법으로 const를 다음과 같이 사용할 수 있다.

   class Stack {
     ...
     bool Full() const;
     ...
   };

   C에서와 같이 변수 선언에 const를 추가해서 변수 값이 변경되지 않아야
   함을 선언할 수 있다.

   const int InitialHashTableSize = 8;

   #define을 통해 상수에 대한 이름을 정의할 수 있지만 const를 사용하는
   것이 컴파일러에 의해 타입 검사를 할 수 있으므로 더 나은 방법이다.

6. C++에서는 >>과 << 연산자와 cin과 cout 객체를 통해서 입출력을 한다. 예를
   들어 stdout에 문자열을 출력하기 위해서

   cout << "Hello world!  This is section " << 3 << "!";

   C에서 위와 동일한 의미의 코드는

   fprintf(stdout, "Hello world!  This is section %d!\n", 3);

   두 코드의 다른 점은 C++에서는 타입을 컴파일러가 자동으로 검사한다는
   점이다. 예를  들어 위의 C코드에서  3대신 3.0을 주어도  C 컴파일러는
   아무런 문제없이 컴파일 할 것이다.

   C++에서 <<와 printf를 함께 사용할 수 있지만 동일한 stream에 함께 사용하지
   않는 편이 낫다.

   int field1, field2;
   cin >> field1 >> field2;
     // fscanf(stdin, "%d%d", &field1, &field2);

   참고로 cin과  cout은 보통의 C++  객체이다. 위의 예제에서  추측할 수
   있듯이 operator overloading(동일한 이름의 연산자 <<와 >>를 사용해서
   여러  타입들의  데이터를  입출력함)과 reference  parameter(데이터를
   읽을 때 해당 변수의 주소를 명시적으로 주지는 않았지만 변수의 주소가
   인자로 전달됨)를  사용한다. 5장에서 설명하겠지만  이러한 C++ 특징은
   사실  피해야할  특징들로 분류하므로  I/O를  위해서 이러한  특징들이
   구현에 어떻게 필요한지 특별히 이해할 필요는 없다.

4 Advanced Concepts in C++: Dangerous but Occasionally Useful
단일상속과 템플릿과  같은 몇가지  C++의 특징들은 잘못  사용하기 쉽지만
일단 잘 사용하면 프로그램을 크게 간단하게 만들수 있다.

지금까지 설명한  C++ 특징들은 사실  C 특징들과 근본적으로  차이가 없는
것들이었다.  사실  능숙한 C  프로그래머는  하나의  자료 구조에  연관된
모듈들에  관련  함수들을 모아놓도록  프로그램을  구성할  줄 알고  있고
심지어는  함수나  변수  이름을  선택할때에도 C++  프로그램을  흉내내서
프로그래밍 하고 있다. 예를 들어 StackFull()이나 StackPush()와 같은 함수
이름은 클래스 이름과 함수 이름을 합해서 만든 것이다.

하지만  이제 설명하려고 하는  단일상속과 템플릿은  프로그래밍에 있어서
일종의  패러다임  전환을  요구하는  특징들로 이를  일반  C의  특징으로
변환하는 것이 간단하지 않다. 이들 특징을 사용할 때 얻을 수 있는 장점은
다양한  성질의 객체에  적용할 수  있는 generic  코드를 만들  수 있다는
것이다.

그럼에도 불구하고 C++을 처음 프로그래밍 하는 경우 단일 상속이나 템플릿과
같은 특징을 사용하지 않아야 한다. 왜냐하면 대부분 이들 특징들을 제대로
사용하지 못할 것이기 때문이다.

4.1 Inheritance

inheritance  특징으로  객체들의  어떤  클래스들을 서로  관련이  있음을
프로그램에 반영할 수 있다. 예를 들어 리스트와 정렬된 리스트는 사용자가
각  리스트에   어떤  원소를  새로  추가하거나   없애거나  찾는  연산을
허용한다는 점에서 유사하다.

inheritance의 두가지 장점은 다음과 같다.

첫째는 객체의  종류에 generic  code를 만들 수  있다. 이  코드는 자신이
다루는 객체의 종류가  무엇인지 몰라도 코드 실행할 수  있다.  예를 들어
윈도우  시스템에서 스크린에  있는 모든  것 (윈도우,  스크롤바, 타이틀,
아이콘 등)이 객체이다. 모든 객체가 공통으로 갖고 있는 멤버 함수로 예를
들어 해당객체의 내용을 화면에  다시 그리는 함수 repaint()가 있다. 이런
상황에서 전체  화면을 다시 그리려면 각 객체의  repaint() 함수를 한번씩
호출하면 된다. 모든 객체가 이 함수를 멤버로서 포함한다면 객체의 종류를
모르고도 이 함수를 호출할 수 있다.

둘째는  여러 객체들이  구현 코드를  공유할 수  있다. 예를  들어 리스트
클래스와 정렬된  리스트 클래스를 구현하는 경우를  고려하자.  두 클래스
모두 리스트에 대한 멤버  함수 insert, delete, isFull, print를 포함해야
한다.그리고 isFull이나 print 함수는  각 클래스에 따라 달라야 할 이유가
없다. 따라서  리스트 클래스를 먼저 구현하고 공유하지  않을 멤버 함수만
따로 수정하고  공유할 수  있는 멤버 함수는  모두 동일한  코드를 그대로
사용해서 정렬 리스트 클래스를 구현할 수 있다.

4.1.1 Shared Behavior

이전의 Stack  클래스는 스택을  구현하기 위해 배열을  이용했지만 대신에
리스트를  이용할 수도  있다.   이전의 Stack  클래스를 사용하는  코드는
리스트를 이용해서  새로 정의한  Stack 클래스를 사용해도  아무런 문제가
없어야 한다.  (물론 리스트 기반  클래스는 오버플로우 문제가  없고 배열
기반  클래스는 오버플로우  문제가 있을  수 있지만  배열  기반 클래스의
경우도 오버 플로우가 나면  새로 배열의 크기를 조정하는 코드를 추가하면
오버플로우 발생 가능성 측면에 있어서도 두 클래스의 차이가 없어진다.)
배열과  리스트를 각각  이용하는 클래스를  함께 사용하기  위해서 다음과
같이 public 멤버 함수만을 포함하는 abstract 스택을 정의한다.

class Stack {
  public:
    Stack();
    virtual ~Stack();
    virtual void Push(int value) = 0;
    virtual bool Full() = 0;
};
Stack::Stack {}      // g++의 경우 초기화할 필드가 없어도
Stack::~Stack() {}   // constructor와 destructor를 정의해야 한다.

위의  Stack  클래스 정의를  base  class  혹은  superclass라 한다.   이
클래스로 부터  두가지 다른 derived  class 혹은 subclass들을  정의할 수
있다.   base class로  부터  정의한 derived  class  들은 원래  클래스의
behavior를  따른다.  (어떤  클래스의  behavior를  따른다  혹은  동일한
behavior를  갖는다라고 말할때는  그 클래스의  멤버의 일부  혹은 전부를
갖는다는 의미다.)

base class에  있는 함수 선언에  사용된 virtual 키워드는  derived class
들에서 이들  함수를 다시 정의할 수 있음을  표시한다.  virtual 함수들은
0으로  초기화하도록  정의되어  있는데  이것은  이들  함수들을  derived
class에서   반드시   정의해야    한다는   것을   컴파일러에게   알리는
것이다. (몇가지  질문: 0이 아닌 다른 값으로도  초기화가 가능한가? 만일
초기화를  하지  않았다면  derived  class에서  정의할  수  없다는  뜻은
아닌가? 실제 컴파일러가 초기화문을 어떻게 구현하는가? )

다음은 배열과 리스트에 기반한 derived class인 ArrayStack과 ListStack을
정의한  예이다. 아래의  예에서 각  derived class를  정의할때  : public
Stack 구문을 사용하는데 이것은 두 derived class가 base class인 Stack의
일종으로 base class와 동일한 behavior를 갖음을 표시한다.

class ArrayStack : public Stack {
  public:
    ArrayStack(int sz);
    ~ArrayStack();
    void Push(int value);
    bool Full();
  private:
    int size;
    int top;
    int *stack;
};

class ListStack : public Stack {
  public:
    ListStack();
    ~ListStack();
    void Push(int value);
    bool Full();
  private:
    List *list;
};

ListStack::ListStack() {
    list = new List;
}

ListStack::~ListStack() {
    delete list;
}

void ListStack::Push(int value) {
    list->Prepend(value);
}

bool ListStack::Full() {
    return FALSE;
}

일단  super class와  derived class로  정의하면 derived  class의 객체에
대한 포인터는 super class 타입의 변수에 저장해서 그 객체들이 마치 super
class의  객체인  것  처럼  사용할  수  있다.   예를  들어  ListStack과
ArrayStack의 객체에  대한 포인터를 Stack 타입의 변수에  저장할 수 있고
이 객체들을 Stack 클래스이 객체인 것처럼 동일하게 다룰 수 있다.

Stack *s1 = new ListStack;
Stack *s2 = new ArrayStack(17);
if (!s1->Full())
    s1->Push(5);
if (!s2->Full())
    s2->Push(6);
delete s1;
delete s2;

위의 프로그램에서 s1에 대해서는  ListStack의 멤버 함수를 s2에 대해서는
ArrayStack 멤버  함수를 호출한다.  이런 형태로  멤버 함수를 호출하려면
컴파일러의 적절한 도움이 필요하다. 각 객체에 대해서 procedure 테이블이
마련되어서  derived class의  객체를  만들때 base  class에 의해  정의된
테이블의  디폴트 내용을 적절히  재수정 (override)한다.   위의 예제에서
Full, Push, delete를  수행할 때 해당 객체에 마련된  테이블 내용을 참조
(indirection)해서 호출해야할  함수가 실제로 어느  함수인지를 결정한다.
따라서 객체의 실제 타입은 알 필요가 없다.

위의 예에서 abstract class인  Stack으로 부터 직접 객체를 만들지는 않기
때문에 virtual 멤버 함수들을 구현할 필요는 없다. Stack 클래스는, Stack
클래스를  서로 다른 방식으로  구현하는 클래스들이  공유하는 behavior가
무엇인가를 지정하는 역할을 한다.

Stack    클래스의   destructor는    virtual    멤버함수로   선언했지만
constructor는 그렇지 않다. 객체를  새로 만들때 이 객체가 어느 종류인지
즉 ArrayStack  클래스에 속하는지 ListStack  클래스에 속하는지를 알아야
한다. 컴파일러는 abstract class인  Stack으로 부터 직접 객체를 만들려고
하는 코드가 있다면 컴파일  에러를 낼 것이다. 실수로라도 abstract class
(정의되지 않는 멤버 함수를  포함하는 클래스)로 부터 직접 객체를 만들수
없다.

위의 예제에서 객체를 없애려고 할 시점에, s1과 s2의 타입은 모두 Stack에
대한 포이터  타입으로 ListStack이나 ArrayStack  타입으로 부터 만들어진
것이라는 정보를 컴파일러가 알지  못한다. 즉 컴파일러는 위의 delete s1;
delete  s2; 코드로  부터 Stack  클래스의 destructor  멤버  함수를 통해
객체를 없애려고 할 것이다. 이는 derived 객체 s1과 s2의 destructor 멤버
함수를 통해  이들 객체를 없애려는 우리의 의도와  다르다. 이런 이유에서
abstract  클래스  Stack의  destructor  멤버  함수를  virtual로  선언한
것이다.

만일  예제에서   destructor  함수를  선언하는   것  자체를  생략했다면
컴파일러는 virtual 멤버 함수가  아닌 함수로 destructor 함수를 자동으로
클래스에 추가할  것이고 이것은 우리의 의도와 달리  Stack 클래스에 대한
메모리는  해제하더라도 ArrayStack과  ListStack 클래스에  대한 메모리는
해제하지 않아서 memory leak을 초래할 수 있다.

4.1.2 Shared Implementation

여기서는 inheritance의  Shared behavior  외의 다른 장점인  코드 공유를
설명한다.  C++에서는  base class의  멤버를  (멤버  함수의 코드와  멤버
변수의 데이터) derived class들에서 사용할 수 있다.
뒤에서  설명하겠지만 inhertance를 이런  형식으로 사용하는  것은 그다지
바람직하지 않다.

Stack,    ArrayStack,   ListStack    클래스에    새로운   멤버    함수
NumberPushed()를  추가해서 스택에 있는  원소의 수를  별도로 저장하려고
한다. ArrayStack 클래스에 이  정보가 이미 있으므로 ArrayStack 클래스의
관련 멤버  변수와 코드를 그대로 중복시켜 ListStack  클래스를 고칠 수도
있다. 하지만 이상적인 방법은 두 클래스가 동일한 코드를 사용하도록 하는
것이다. C++의 inheritance 특징을 가지고 카운터를 Stack 클래스로 옮겨서
derived  class에서  base  class의  멤버 함수를  호출해서  이  카운터를
업데이트 할 수 있다.

class Stack {
  public:
    virtual ~Stack();
    virtual void Push(int value);
    virtual bool Full() = 0;
    int NumPushed();
  protected:
    Stack();
  private:
    int numPushed;
};

void Stack::Stack() {
    numPushed = 0;
}

void Stack::Push(int value) {
    numPushed++;
}

int Stack::NumPushed() {
    return numPushed;
}

Stack의 추가된  behavior를 이용하도록 ArrayStack과  ListStack 클래스를
변경할 수 있다. 아래에서는 ArrayStack 클래스만 예로 든다.

class ArrayStack : public Stack {
  public:
    ArrayStack(int sz);
    ~ArrayStack();
    void Push(int value);
    bool Full();
  private:
    int size;
    int *stack;
};

ArrayStack::ArrayStack(int sz) : Stack() {
    size = sz;
    stack = new int[size];
}

void
ArrayStack::Push(int value) {
    ASSERT(!Full());
    stack[NumPushed()] = value;
    Stack::Push();
}

위의 예제에서 몇가지 설명을 덧붙인다.

  1. ArrayStack 클래스의  constructor 함수는 Stack  클래스의 numPushed
     멤버변수를 초기화하기  위해서 Stack 클래스의  컨스트럭터를 호출할
     필요가 있다. : Stack()를 사용한다.

     ArrayStack::ArrayStack(int sz) : Stack()    

     destructor  함수의  경우도  유사하게  base  class의  destructor를
     호출할  수   있다.   C++   언어  매뉴얼에  의하면   base  class의
     constructor/destructor         함수        derived        class의
     constructor/destructor  함수를 어떤  순서로 호출할  것인지에 대한
     규칙이  있다. 그러한  규칙에 따라  프로그램을 하면  이 프로그램을
     읽는 사람이  C++ 매뉴얼을 참조해야 프로그램을  이해할 수 있으므로
     이러한  규칙에 의존하는  프로그램을 처음부터  작성하지  않는 것이
     좋다.
     (규칙이 무엇인지??  위에서 Stack()을 대신할 수 있는  식은 다른 것
      어떤 것이 있는지??)

  2. Stack 클래스에서 새로운  키워드 protected를 사용한다. protected로
     선언된  멤버  함수와 멤버  변수는  현재  클래스로부터 derived  된
     클래스에서 사용  가능하지만 다른 클래스에서는  사용 가능하지 않은
     멤버이다.  즉   protected로  선언된  멤버는   derived  클래스에는
     public으로 해석하고 그 외의 클래스에는 private으로 해석한다.
     위의  예제에서 Stack  클래스의 컨스트럭터  Stack()은 ArrayStack과
     ListStack  클래스에서 사용할  수 있고  그 외에서  사용하면 컴파일
     에러가 날 것이다.

     Stack   클래스의  멤버  데이터를   모두  private   속성을  갖도록
     정의하였다. 여러가지 스타일이 가능하지만, 클래스 내에 있는 어떠한
     데이터도 다른  클래스에서 직접  사용할 수 없도록  하는 프로그래밍
     스타일을  따를  것을   추천한다.  inheritance로  연관된  클래스들
     사이에서도 이  스타일을 그대로 따른 것을  추천한다. 왜냐하면 만일
     base class의 구현 방법을  변경하면 관련 derived class의 모든 구현
     방법을  검사해서 필요한  경우에 바꿔야  하는데  이것은 프로그램의
     모듈화라는 측면에서 바람직하지 않다.

  3. base  클래스에 대해서  정의한 모든  함수는 derived  클래스의 멤버
     함수로써  자동으로   간주한다.  예를  들어   ArrayStack  클래스에
     NumPushed()  함수가 정의되어  있지 않지만  다음과 같이  사용할 수
     있다.

     ArrayStack *s = new ArrayStack(17);
     ASSERT(s->NumPushed() == 0);

  4. Stack::Push()   함수가  virtual로   선언되어   있지만  ArrayStack
     클래스의 객체를  가리키는 Stack 타입 포인터를  통해 Push() 함수를
     호출하면  ArrayStack  클래스의   Push()  함수를  호출하는  결과를
     얻는다.

     Stack *s = new ArrayStack(17);
     if (!s->Full())
         s->Push(5);

  5. Stack::NumPushed()은 virtual로 선언되어 있지 않다. 따라서 Stack의
     derived 클래스에 의해 재정의 될 수 없다. (overriding이 불가???)
    
     (Some people belive that you  should mark all functions in a base
     class  as virtual; that  way, if  you later  want to  implement a
     derived class that redefined a function, you don't have to modify
     the base class to do so.)

  6. derived 클래스의 멤버  함수로 부터 base 클래스의 public/protected
     멤버  함수를 클래스와  함수  이름의 쌍으로  ( Base::Function()  )
     명시적으로 지정해서 호출할 수 있다.

     void ArrayStack::Push(int value)
     {
         ...
         Stack::Push();
     }

     만일  위  함수  내에서  Stack::Push()  대신  Push()를  호출했다면
     컴파일러는  이를 ArrayStack::Push()로  해석할 것이므로  재귀 함수
     호출  형태가 될  가능성이 있다.  이것은 물론  우리가  의도한 바가
     아니다.

이  외에도  C++언어는  inheritance에  관해  아주  많은  내용을  자세히
포함하고  있다.  inheritance  특징을 사용하는  프로그램의  단점은 구현
내용이 여러 파일에 걸쳐서 흩어져서 정의되는 경향이 있다는 것이다. 만일
inheritance의 트리가  크다면 어떤  멤버 함수를 호출했을때  실제로 어떤
클래스의 어떤 함수의 코드가 실행되는지를 찾는 것이 복잡해진다.

inheritance를 사용해서 프로그램을 작성하기 전에 inheritance를 사용하는
목적이 무엇인지를 심각하게 고려해야 한다. (다시 말하면 inheritance를
사용하는 것을 최대한 줄이라는 얘기!   --- It's very surprising. )

inheritnace를  사용하는  것이  좋은  상황은 객체들  사이에  behavior를
공유할  때이다. 코드를 공유하기  위해서는 절대  inheritance를 사용하지
말아야  한다.   (---  It's  very  interesting because  the  notion  of
inheritance becomes  kind of parametric  polymorphism when it  is only
used for capturing the  shared behavior among objects.) C++에서 두가지
상황에 대해서 모두 사용할  수 있지만 "shared behavior"를 표현하기 위해
inheritance를 사용하면  정말로 읽기 쉬운 간단한  형태의 프로그램을 만들
수 있을 것이다.

(??? To  illustrate the difference between shared  behavior and shared
implementation, suppose  you had a  whole bunch of different  kinds of
objects  that  you  needed  to  put on  lists.   For  example,  almost
everything  in  an operating  system  goes on  a  list  of some  sort:
buffers, threads, users, terminals, etc.
A very common approach to  this problem (particularly among people new
to object oriented programming) is to make every object inherit from a
single  base class  Object, which  contains the  forward  and backward
pointers for the list. But what if some object needs to go on multiple
lists? The whole scheme breaks down,  and it's because we tried to use
inheritance  to share  implementation (the  code for  the  forward and
backward  pointers) instead  of  to share  behavior.   A much  cleaner
(although  slightly  slower)  approach  would  be  to  define  a  list
implementation  that  allocated  forward/backward  pointers  for  each
object that gets put on a list.)

요약하자면,  만일  두  클래스에  몇몇  동일한  멤버  함수  signature를
공유하고 있고 (i.e. the two classes share the same behavior) 그 shared
behavior에만 의존하는 코드가  있다면 (shared behavior의 코드가 아니라)
inheritance를  사용해서 이러한  관계를  표현하는 것이  도움이 될  수도
있다.  (항상 도움이 되는 것이 아니라.) 

4.2 Templates

템플릿은  클래스를  타입에  독립적으로   정의할  수  있게  하는  C++의
특징이다. 지금까지 예로써 든  Stack 클래스의 경우 정수값에 대한 스택을
구현하는 것이다. 만일 문자, 실수, 포인터, 그 외의 임의의 데이터에 대한
스택을 어떻게 구현할 것인가?

template <class T>
class Stack {
  public:
    Stack(int sz);
    ~Stack();
    void Push(T value);
    bool Full();
  private:
    int size;
    int top;
    T *stack;
};

템플릿을 정의하기 위해서 template 키워드를 클래스 정의에 붙이고 필요한
만큼의 타입을 선언한다. <class T1, ..., class Tn>
템플릿 클래스 내의 멤버 함수 역시 필요하다면 템플릿으로 선언해야 한다.

template <class T>
Stack<T>::Stack(int sz) {
    size = sz;
    top = 0;
    stack = new T[size];
}

template <class T>
void
Stack<T>::Push(T value) {
    ASSERT(!Full());
    stack[top++] = value;
}

템플릿 클래스로부터 객체를 만드는 것은 다음과 같다.

void
test() {
    Stack<int> s1(17);
    Stack<char> *s2 = new Stack<char>(23);
    s1.Push(5);
    s2->Push('z');
    delete s2;
}

위의  코드에서 int  타입에 관한  스택 클래스와  char 타입에  관한 스택
클래스를 정의한 것과 동일한 효과를 갖는다. s1은 int 타입을 원소로 하는
스택  클래스의 객체이고  s2는 char  타입을 원소로  하는  스택 클래스의
객체이다. 실제로  템플릿 클래스를  구현할 때 각각의  다른 instantiated
type에 따라 템플릿 클래스 전체를 복사하는 방법을 일반적으로 사용한다.
템플릿  클래스 특징을 이용하면  프로그램을 모듈화하는데  도움이 되지만
템플릿을 사용하는  프로그램을 디버깅하기가 어렵다. 예를  들어 현재 C++
디버거의  경우 템플릿을  그다지 인식하지  못해서 디버깅을  어렵게 하는
경향이 있다. (2006년 지금은???) 물론, 타입만 다르고 거의 비슷한 두개의
구현을 디버깅하는 것보다 템플릿을 디버깅하는 것이 쉬울 수 있다.

최대한 템플릿을  사용하지 않는 편이  좋다.  만일 템플릿을  써야 한다면
일단  템플릿을  사용하지 않고  구현한  다음  이것을 템플릿을  사용해서
바꾸어도 크게 어렵지 않을 것이다.

템플릿을  사용할 때  한가지 문제점은  템플릿 클래스는  각 instantiated
type 별로 복사하는  형태로 구현되기 때문에 코드 크기가  크게 증가할 수
있다.

5. Features To Avoid Like the Plague

저자의 15년 C++ 프로그래밍 경험에 바탕해서 판단하건데 C++에는 사용하지
않는  것이  바람직한  특징들이  많이 포함되어  있다.  왜냐하면  이러한
특징들은  잘못  사용하기  쉽고,  프로그램을 읽고  이해하는데  방해하기
때문이다. 또한 많은 경우 이러한 특징들 없이도 동일한 일을 프로그래밍
할 수 있다.

   1. Multiple inheritance:  여러개의 클래스로부터 behavior를 상속받아
      클래스로 정의하는  것. 단일  상속도 어려운데 다중  상속은 얼마나
      어렵겠는가.

   2. References:  call-by-reference를  지원한다.  reference는  구문만
      다를  뿐  포인터와  동일하다.  call-by-reference를  사용한  함수
      호출의  경우  언뜻  보기에  call-by-value를 사용한  함수  호출과
      비슷하지만  호출한   함수에서  인자값을  변경시킬   수  있으므로
      프로그램을 이해하는데 혼란스럽게 한다.

   3. Operator overloading: 프로그래머로 하여금 operator의 의미를 다시
      정의할 수  있게 한다. 묵시적으로 타입을 변경할  수 있는 가능성이
      많은 C++ 언어의 경우 연산자 중복 기능을 사용하는 것은 바람직하지
      않다. 연산자의  타입에 따라서  실제 그 연산자를  구현하는 코드가
      결정이  되는데 C++  프로그램의 경우  연산자의  타입이 직관적이지
      않은  방법에 의해서  프로그램을 보고  예상했던 것과  다른 타입을
      할당할 수 있고 결과적으로 예상치 못한 코드를 수행할 수 있다.

   4. Function overloading: 클래스  내에서 이름은 갖지만 인자의 타입은
      다른 멤버  함수를 여러개  정의할 수 있다.  프로그램에서 의도하지
      않은 함수를 호출하는 경우가 쉽게 발생할 수 있다.
      다른 클래스에 멤버 함수들에서 동일한 타입의 인자들을 받고 동일한
      의미의  역할을 수행한다면  그 멤버  함수들에 대해서  이름을 갖게
      하는 것은 바람직하다.  (문맥하고 다른 얘기 아닌가???)

   5. Standard template libray: 리스트, 해쉬테이블과 같은 것을 구현하는
      ANSI 표준 라이브러리가 있다.
      (??? Alas,  the standard template libray pushes  the envelope of
      legal  C++, and so  virtually no  compilers (including  g++) can
      support  it  today. Not  to  mention  that  it uses  references,
      operator overloading, and function overloading.)

   6. Exceptions: 함수 호출로 부터 에러를 리턴하는 방법.

C의 특징 중에서 사용하지 말아야 할 것들을 몇가지 나열한다.

   1. Pointer  arithmetic: 통제  불능의 포인터는  C프로그램에서 버그의
      주된  원인 중의  하나이다.  프로그램의  전혀 다른  부분에 위치한
      데이터를 망칠 수 있다. 어떤 객체가 힙에 어느 순서로 할당되는가에
      따라   포인터  에러는   무작위로   나타나기도  하고   사라지기도
      한다. 예를 들어 printf를  프로그램 중간 중간에 삽입하면 printf의
      코드에서  가끔  메모리  할당을  하기 때문에  printf  다음에  new
      연산자로 메모리를 할당하면 이 메모리 주소는 printf를 넣지 않았을
      때와 주소가 다르게 된다. 따라서 printf를 중간 중간에 넣는 것조차
      통제    불능의     포인터로    인해    망치게     될    데이터가
      달라진다. (Surprising!)

      최대한 포인터  연산은 다른 방법으로 바꾸는 것이  좋다. 예를 들어
      포인터를  증가하면서 데이터를 다루는  코드의 경우  별도의 인덱스
      변수를 선언해서  이 인덱스 변수와 데이터의  경계 위치를 비교하는
      코드로  다시 고쳐볼  수  있겠다. 최적화  컴파일러를 사용하면  두
      코드에 대해 거의 같은 성능의 머신코드을 만들어 낼 것이다.

      포인터를 사용하지 않더라도 예를  들어 1 offset만큼 배열의 경계를
      잘못  다뤄 에러를 초래할  수도 있다.  이런 에러를  막기 위해서는
      배열을 참조할  때 항상 배열의 경계를  넘어가지 않는지를 검사하는
      멤버 함수를 포함하는 클래스를 정의해 볼 수 있겠다.

    2. Casts from  integers to pointers  and back: 도대체  왜 포인터를
       정수로 바꾸고 정수를 포인터로 바꿔야 한다는 말인가? 특히 64비트
       머신의  경우 정수의  크기와 포인터의  크기가  다르므로 주의해야
       한다.

    3. Using bit shift  in place of a multiply  or divide: 산술 계산을
       해야  한다면 산술  연산자를 사용하고,  비트 연산을  해야 한다면
       비트 연산자를 사용하는 것이 바람직하다. 초창기 C컴파일러의 경우
       비트 연산자를  사용하면 산술 계산을 더욱 빠르게  수행할 수 있는
       경우도 있었지만 지금은 그렇지 않다.

    4. Assignment inside conditional:

       if (x=y) { ... }

       위와 같은  형태가 프로그래머가 의도한  assignment일 수도 있지만
       혹은 프로그래머가  실수해서 =를 빠뜨린 경우일  수도 있다.  위의
       코드에서   만일   조건문에   assignment를   사용하지   않는다는
       가이드라인이 있었다면 위와 같은  코드를 보고 쉽게 에러라는 것을
       알아낼 수 있다.

    5. Using #define when you could use enum:
       enum으로 정의한 심볼은 컴파일러에 의해 타입이 맞는지를 자동으로
       검사해줄 것이다.

6. Style Guidelines

Nachos 프로젝트 관련 C++ 프로그램 스타일 가이드라인에 대해서 나열한다.

    1. Words in a name are separated SmallTalk-style: 각 새로운 word를
       시작할 때  대문자를 사용. 모든  클래스 이름과 멤버  함수 이름은
       대문자로 시작. get/set 멤버 함수는 예외.

    2. 모든 전역  함수의 이름은 대문자로 시작.  main 함수와 라이브러리
       함수는 예외.

    3. 전역 변수를 사용하는 것을 최소화. 많은 전역변수가 사용이 된다고
       생각하면 관련있는 전역 변수들을 모아서 하나의 클래스로 만들거나,
       인자로 함수에 전달할 수 있는 방법을 찾는다.

    4. 전역  함수를  사용하는 것을  최소화.  어떤  특정 객체에  대해서
       연산하는 함수라면 그 객체의 클래스 멤버 함수로 포함시키라.

    5. 모든 클래스에  대해서 .h 파일과 .cc 파일을  별도로 만들어라. .h
       파일은  클래스에 대한  인터페이스로, .cc  파일은  클래스에 대한
       구현의 역할을 한다.

       (단일 의존 관계)
       하나의 .h  파일이 다른 .h 파일을 포함해야  한다면 이 의존관계를
       .h  파일에 포함시켜라.  (앞의  .h 파일에  #include <뒤의  .h>를
       추가) 

       (DAG 의존 관계)
       만일 여러 개의 .h 파일이 포함되는 경우 각 .h 파일에 다음과 같은
       코드를 추가한다.

       #ifndef STACK_H
       #define STACK_H
       class Stack { ... };
       #endif
      
       (Cyclic  의존  관계)  두  개의 .h간에  싸이클  형태  의존관계를
       갖는다면 ad-hoc한 방법을 고려해야 한다. 기본 전략은 의존하는 .h
       파일의 내용을 항상 모두 포함할 필요는 없는 경우를 찾는 것이다.
       예를 들어  어떤 클래스에  대한 포인터는 사용하지만  그 클래스의
       멤버 함수나 멤버 변수를 사용하지 않는다면 stack.h를 include하는
       대신  아래의 선언을 해서  컴파일러에 Stack이라는  클래스 이름을
       알릴 수 있다.

       class Stack;
    
       가끔  이런 전략이  적용할  수 없는  경우에는  클래스 정의  등을
       적절히 옮겨서 재배치 해야 한다.

    6. ASSERT 문을  적극 활용하라. ASSERT 문은  조건문으로 이 조건문이
       FALSE로  결정되면  어떤  가정이  어긋나서  버그가  있음을  미리
       찾아낸다.  ASSERT문이  없을 경우에는 이  어긋난 가정으로 인해서
       프로그램 어딘가에서 에러가 나고 이를 사용자가 눈치챌만한 상황이
       벌어지고 나서야 비로소 이 버그를 발견할 수 있을 것이다.

       ASSERT 문은  특히 함수의  시작과 끝에서 함수의  인자와 리턴값에
       대한 가정을 검사할 수 있다.

       속도가  문제라면 ASSERT는  디버그 버젼에서만  나타나도록 셋팅할
       수도 있다. 제품 수준에서도 ASSERT 문을 포함하는 경우도 많다.

    7. Write  a module  test  for  every module  in  your program:  각
       모듈별로   테스팅을  반드시  한   다음  전체   시스템  수준에서
       테스팅하라.

7. Compiling and Debugging

8. Example: A Stack of Integers

9. Epilogue

프로그래밍  언어에서 어떤  특징을 가지고  있다면 이  특징을 포함시켜야
하는 좋은 이유가 반드시 있어야 한다. 모든 프로그래머는 코드를 쓸 때 이
코드의  동작을  다른  사람이  즉시  분명하게  이해할  수  있도록  해야
한다.  코드를 작성할  때  얼마나  많은 문자를  썼는지  보다 이  코드가
동작하고 다른 사람이 얼마나 간단하게 수정할 수 있는지가 더욱 중요하다.

"There are two  ways of constructing a software design:  one way is to
make it  so simple  that there are  obviously no deficiencies  and the
other  way is  to make  is so  complicated that  there are  no obvious
deficiencies."

C.A.R. Hoare, "The Emperor's Old Clothes", CACM Feb 1981.

10 Further Reading

- Jamse Coplien, "Advanced C++", Addison-Wesley.
- James Gosling. "The Java Language."
- C.A.R.  Hoare,  "The  Emperor's  Old  Clothes."  CACM,  Vol.24,  No.2, February 1981, pp75-83.
- B.Kernighan and D.Ritchie, "The C Programming Language", Prentice-Hall.
- Steve Maguire, "Writing Solid Code", Microsoft Press.
- Steve Maguire, "Debugging the Development Process", Microsoft Press.
- Scott Meyers, "Effective C++".
- Bjarne Stroustrup, "The C++ Programming Language", Addison-Wesley.


11. 그 외 해로운 features

static & friend

멤버 함수 선언은 다음의 세가지 사항을 지정한다.
    1) 이 멤버 함수는 해당 클래스 선언의 private 영역을 접근할 수 있다.
    2) 이 멤버 함수는 클래스의 scope 내에 있다.
    3) 이 밤수는 반드시 객체를 통해 호출해야 한다.

static 키워드는 1)과 2)를 지정하고, friend 키워드는 1)을 지정한다.

private & proctected & public 멤버
클래스 멤버는 private, protected, public 세가지로 지정할 수 있다.

    a) private  멤버는 멤버 함수와 그 클래스의  friend에 의해서 접근할
       수 있다.
    b) protected 멤버는 멤버 함수와 그 클래스의 friend에 의해서 접근할
       수 있고,  그 클래스로 부터  유도된 클래스의 멤버  함수와 유도된
       클래스의 friend에 의해서 접근할 수 있다.
    c) public 멤버는 어느 함수에 의해서도 접근 가능하다.

private & proctected & public 유도
D : ( private | protected | public ) B

    a) B 클래스가 private base이면
         a-1)  D  클래스의  멤버  함수와 friend는  B의  public  멤버와
              protected 멤버를 사용할 수 있다.
         a-2)  클래스 D의 멤버 함수와 friend에 의해서만 D*를 B*로 변환할
               수 있다.

    b) B 클래스가 proctected base이면
         b-1)  D  클래스의  멤버  함수와 friend는  B의  public  멤버와
              protected 멤버를 사용할 수 있고, D 클래스로 부터 유도된
              클래스의 멤버 함수와 friend에 의해 B의 public 멤버와 procteded
             멤버를 사용할 수 있다.
         b-2)  D 클래스의 멤버 함수와 friend 그리고 D 클래스로 부터 유도된
               멤버 함수와 friend에 의해서만 D*를 B*로 변환할 수 있다.

    c) B 클래스가 public이면
         c-1)  어느 함수도 B 클래스의 public 멤버를 사용할 수 있다. B의
               proctected 멤버를 D의 멤버와 friend 그리고 D로 부터 유도된
               클래스의 멤버와 friend가 사용할 수 있다.
         c-2)  어느 함수도 D*를 B*로 변환할 수 있다.

EOF

No comments: