[C++] 상속

C++ 2017. 12. 25. 19:30

1] 상속의 문법적인 이해
1. 상속의 방법과 그 결과

class Person
{
private:
    int age;
    char name[50];
public:
    Person(int myage, char* myname) : age(myage)
    {
        strcpy(name, myname);
    }
    
    void WhatYourName() const
    {
        cout<<"My name is "<<name<<endl;
    }
   
    void HowOldAreYou() const
    {
        cout<<"i'm "<<age<<" years old"<<endl;
    }
};

class UnivStudent : public Person
{
private:
    char major[50];
public:
    UnivStudent(char* myname, int myage, char* mymajor) : Person(myage, myname)
    {
        strcopy(major, mymajor);
    }
    
    void WhoAreYou() const
    {
        WhatYourName();
        HowOldAreYou();
        
        cout<<"My major is "<<major<<endl;
    }
};

위 코드에서 class UnivStudent : public Person 부분이 상속 문법이다. pulbic의 의미는 나중에 살펴보기로 하자. 

2. 자식(유도) 클래스의 객체 생성과정

class SoBase
{
private:
    int baseNum;
public:
    SoBase() : baseNum(20)
    {
        cout<<"SoBase()"<<endl;
    }
    
    SoBase(int n) : baseNum(n)
    {
        cout<<"SoBase(int n)"<<endl;
    }
    
    void ShowBaseData()
    {
        cout<<baseNum<<endl;
    }
};

class SoDerived : public SoBase
{
private:
    int derivNum;
public:
    SoDerived() : derivNum(30)
    {
        cout<<"SoDerived()"<<endl;
    }
    
    SoDerived(int n) : derivNum(n)
    {
        cout<<"SoDerived(int n)"<<endl;
    }
   
    SoDerived(int n1, int n2) : SoBase(n1), derivNum(n2)
    {
        cout <<"SoDerived(int n1, int n2)"<<endl;
    }

    void showDerivData()
    {
        ShowBaseData();
        cout<<derivNum<<endl;
    }
};

int main(void)
{
    cout<<"case1....."<<endl;
    SoDerived dr1;
    dr1.ShowDerivData();
    cout<<"-------------"<<endl;

    cout<<"case2...."<<endl;
    SoDerived dr2(12);
    dr2.ShowDerivData();
    cout<<"-------------"<<endl;

    cout<<"case3...."<<endl;
    SoDerived dr3(23, 34);
    dr3.ShowDerivData();
    cout<<"--------------"<<endl;

    return 0;
}

-> 결과
case1.....
SoBase()
SoDerived()
20
30
-------------

case2.....
SoBase()
SoDerived(int n)
20
12
-------------

case3.....
SoBase(int n)
SoDerived(int n1, int n2)
23
34
-------------

결론
: 자식 클래스의 객체생성 과정에서 부모 클래스의 생성자는 100% 호출된다.
: 자식 클래스의 생성자에서 부모 클래스의 생성자 호출을 명시하지 않으면, 부모 클래스의 void 생성자가 호출된다.

3. 자식(유도) 클래스의 객체 소멸과정
: 생성 과정과는 반대로 자식 클래스의 소멸자가 먼저 불리고, 부모 클래스의 소멸자가 나중에 불린다. 


2] 세 가지 형태의 상속
: protected는 알고있다 시피 상속 클래스에서의 접근을 허용한다. 그러나 상속관계 에서도 정보은닉의 원칙을 지키는 것이 좋기 때문에 자주 사용되지는 않는다. 

class Base
{
private:
    int num1;
protected:
    int num2;
private:
    int num3;
}

1. protected 상속
: protected 상속의 의미는 "protected 보다 접근 범위가 넓은 멤버는 protected로 변경시켜서 상속하겠다." 는 뜻이다. 

class Derived : protected Base
{
접근불가:     
    int num1;
protected:
    int num2;
protected:
    int num3;
};

Base 클래스에서 num1은 private 으로 선언되었기 때문에 자식 클래스인 Derived 에서도 접근이 불가능 하다. num3은 public으로 protected 보다 접근범위가 넓기 때문에 protected로 바껴서 상속된다. 

2. private 상속
: private 상속의 의미는 "private 보다 접근 범위가 넓은 멤버는 private로 변경시켜서 상속하겠다." 라는 뜻이다. 

class Derived : private Base
{
접근불가:
    int num1;
private:
    int num2;
private:
    int num3;
};

이 경우 Derived 클래스를 다른 클래스가 상속한다면 접근할 수 있는 멤버변수가 하나도 없게 된다.

3. public 상속
: public 상속의 의미는 public 보다 넓은 접근 범위는 없기 때문에, 바꿔 말하면 이렇게 된다. 
"private을 제외한 나머지는 그냥 그대로 상속한다." 

사실 protected, private 상속은 다중상속과 같이 특별한 경우가 아니면 잘 사용하지 않는다. 


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

[C++] 템플릿(Template)(1)  (0) 2017.12.25
[C++] 상속과 다형성  (0) 2017.12.25
[C++] Friend와 Static 그리고 Const  (0) 2017.12.25
[C++] 복사 생성자(Copy Constructor)  (0) 2017.12.23
[C++] 클래스의 완성  (1) 2017.12.23
Posted by 홍성곤
,

1] Const
1. 객체의 상수화
: C++ 에서는 변수에 const를 붙이는것 이외에 객체에도 const를 붙일 수 있다. 

const SoSimple sim(20);

이렇게 객체에 const가 붙게 되면, 이 객체를 대상으로는 const 멤버함수만 호출이 가능하다. 
즉, 이 객체의 데이터 변경을 허용하지 않는다. 

2. const와 함수 오버로딩
: 함수 오버로딩이 성립하려면 매개변수의 수나 자료형이 달라야 한다. 하지만 const 선언유무도 함수 오버로딩의 조건에 해당이 된다. 

void SimpleFunc() { ... }
void SimpleFunc() const { ... }

즉, const로 선언된 객체가 함수를 호출하면 const 함수가 불리고 일반 객체가 함수를 호출하면 일반 함수가 호출된다. 

2] 클래스와 함수에 대한 friend 선언
1. 클래스의 friend 선언
: friend 선언은 private 멤버의 접근을 허용하는 선언이다. 

class Girl; // Girl 이라는 이름이 클래스의 이름임을 알림

class Boy
{
private:
    int height;
    friend class Girl;  // 사실 위에 class  Girl; 문장이 없어도 된다. 이 문장은 Girl이 클래스라는 사실과 Girl 클래스가 friend 라는 사실을 동시에 알려준다. 
};

class Girl
{
private:
    char phNum[20];
public:
    Girl(char* num)
    {
        strcpy(phNum, num);
    }
    
    void ShowYourFriendInfo(Boy &frn)
    {
        cout << "His height: "<<frn.height << endl;
    }
};

Boy 클래스가 Gril 클래스를 Friend 선언했다. 그래서 Girl 클래스는 Boy 클래스의 private 멤버의 접근이 가능하다. 그 반대는 안된다. 

* friend 선언은 얽히고 설킨 클래스의 관계를 friend로 풀려는 사람들을 종종 볼수 있다. 이 방법은 friend 사용의 잘못된 예이다. 정보의 은닉성을 훼손하는 행위이기 때문이다. friend 사용은 최대한 소극적으로 하는게 맞다. 다만, 이후에 배울 연산자 오버로딩에서 효율적으로 friend를 사용하는 방법을 배울것이다. 

2. 함수의 friend 선언
: 전역함수를 대상으로도, 클래스 멤버함수를 대상으로도 friend 선언이 가능하다. 

class Point;

class PointOP
{
private:
    int opcnt;
public:
    PointOP() : opcnt(0) {  }
    
    Point PointAdd(const Point&, const Point&);
};

class Point
{
private:
    int x;
    int y;
public:
    Point(const int &xpos, const int &ypos) : x(xpos), y(ypos) {  }
   
    friend Point PointOP::PointAdd(const Point&, const Point&);
    friend void ShowPointPos(const Point&);
};

Point PointOP::PointAdd(const Point& pnt1, const Point& pnt2)
{    
    opcnt++;
    return Point(pnt1.x+pnt2.x, pnt1.y+pnt2.y);
}

void ShowPointPos(const Point& pos)
{
    cout<<"x: "<<pos.x<<endl;
    cout<<"y: "<<pos.y<<endl;
}

위 코드에서 Point클래스에서 PointOP 클래스의 PointAdd 멤버함수를 friend 선언했기 때문에, PointAdd 멤버함수 에서는 Point의 private 멤버의 접근이 가능하다. 
또한 Point클래스에서 ShowPointPos() 전역 함수도 friend 선언했기 때문에 ShowPointPos() 전역함수에서도 Point 클래스의 private 멤버의 접근이 가능하다.

그리고 Point 클래스에서 friend void ShowPointPos(const Point&); 선언은 friend 선언 이외에 함수 원형 선언의 의미도 포함되어 있어서 따로 함수 원형 선언을 할 필요가 없다. 


3] C++ 에서의 static 
1. C언어에서 이야기한 static
- static 전역변수 
: 선언된 파일 내에서만 참조를 허용하겠다는 의미
- 함수 내에 선언된 static 의미
: 한번만 초기화되고, 지역변수와 달리 함수를 빠져나가도 소멸되지 않는다. 

2. static 멤버 변수
: 멤버 변수를 static 으로 선언하면, 해당 변수는 instance 마다 하나씩 존재하는 변수가 아니라 클래스에 하나만 존재하는 변수이고, 모든 instance가 해당 변수를 공유한다. 

class SoSimple
{
private:    
    static int simObjCnt;
public:
    SoSimple()
    {
        simObjCnt++;
    }    
};

int SoSimple::simObjCnt = 0;

위 코드에서 simObjCnt는 static 멤버변수이다. instance가 생성될 때 마다 값이 1 증가할 것이다. 
int SoSimple::simObjCnt = 0; 는 static 멤버변수를 초기화 할때의 문법이다. 풀어서 해석하면 "simObjCnt가 메모리 공간에 할당될때 0으로 초기화 해줘라" 라는 뜻이다.

3. static 멤버변수의 또 다른 접근방법
: static 멤버변수는 public으로 선언되기만 하면 어디서든 접근이 가능한 변수다.
위 SoSimple 클래스에서 simObjCnt가 public으로 선언되어 있으면 아래와 같은 문법으로 접근이 가능하다. 

SoSimple sim;

SoSimple.simObjCnt;
sim.simObjCnt; // 이 문법은 인스턴스 멤버변수에 접근하는것과 같은 착각을 일으킨다. 가급적 클래스 이름으로 접근하는 방법을 택하자. 

4. static 멤버 함수
: 개념은 static 멤버 변수와 같다. 단지 변수가 함수로 바뀌었을 뿐이다. 그리고 static 멤버 함수 안에서는 인스턴스 멤버변수에 접근할 수 없다. 
즉, static 멤버 함수 안에서는 static 멤버 변수와 static 멤버 함수만 호출이 가능하다. 

5. const static 멤버
: 이전에 배웠듯이 const 멤버변수(상수)는 이니셜라이저를 통해서만 초기화할 수 있었다. 그러나 const static으로 선언되는 멤버변수는 다음과 같이 선언과 동시에 초기화가 가능하다. 

class CountryArea
{
public:
    const static int RUSSIA = 1707540;
    const static int CANADA = 998467;
};

6. 키워드 mutable
: "mutable" 키워드의 의미는 "const 함수 내에서의 값의 변경을 에외적으로 허용한다." 이다. 설명만 들어도 가급적 사용하면 안될것 같은 느낌이 든다.

class SoSimple
{
private:
    mutable int num2;
public:
    void setNum2(int num) const
    {
        num2 = num;
    }
};

위 코드와 같이 setNum2() 는 const 멤버함수로 선언되어 있는데도 불구하고 num2 멤버 변수가 mutable 선언되어 있기 때문에 const 멤버함수에서 예외적으로 값 변경이 가능하다. 



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

[C++] 상속과 다형성  (0) 2017.12.25
[C++] 상속  (0) 2017.12.25
[C++] 복사 생성자(Copy Constructor)  (0) 2017.12.23
[C++] 클래스의 완성  (1) 2017.12.23
[C++] 클래스의 기본  (0) 2017.12.23
Posted by 홍성곤
,

1] 복사 생성자?
1. C++ 스타일의 초기화
우리는 지금까지 밑의 방법으로 변수 및 참조자(reference)를 초기화해 왔다.

int num = 20;
int &ref = num;

하지만 C++ 에서는 다음의 방식으로 초기화가 가능하다. 

int num(20);
int &ref(num);

즉, int num이 int num(20)과 의미가 동일하다. 이것을 객체와 연관시켜보자. 

class SoSimple
{
private:
    int num1;
    int num2;
public:
    SoSimple(int n1, int n2) : num1(n1), num2(n2) { }
}
       
int main(void)
{
    SoSimple sim1(15, 20);
    SoSimple sim2 = sim1;

    return 0;
}

위 코드에서 sim2는 sim1에 멤버변수값을 깊은 복사하게 된다. 

이전에 살펴봤듯이, 

SoSimple sim2 = sim1
SoSimple Sim2(sim1)

위 두 문장은 동일한 의미로 해석된다. 
SoSimple Sim2(sim1) 코드가 정상적으로 작동하려면 SoSimple 객체를 인자로 받는 생성자가 있어야 되는것 아닌가? 그렇다! 이 생성자가 바로 복사 생성자이고, 이것은 컴파일러에 의해서 자동으로 생성된다. 
즉, 아래와 같은 복사 생성자가 자동으로 삽입된다.

SoSimple(const SoSimple& copy) : num1(copy.num1), num2(copy.num2) {  } 

2. explicit로 복사 생성자의 묵시적 호출을 막자!
SoSimple sim2 = sim1은 SoSimple sim2(sim1)로 컴파일러에 의해 변환되어 복사생성자가 호출된다. 즉, 프로그래머가 의도하지 않더라도 컴파일러에 의해 호출되는 것을 묵시적 호출이라 부른다. 
다만, 위와 같은 묵시적 호출이 마음에 들지 않으면 "explicit" 키워드를 사용해서 막을 수 있다.

explicit SoSimple(const SoSimple& copy) : num1(copy.num1), num2(copy.num2) {  } 

위와같이 선언하면, 복사생성자를 호출하기 위해서는 명시적으로 호출해야 한다. 
그리고 이와같은 묵시적 호출은 복사 생성자에게만 일어나는 것이 아니다. 전달인자가 하나인 생성자가 있다면, 이 역시 묵시적 변환이 발생한다. 다음 코드를 보자. 

class AAA
{    

private:       
    int num;
public:
    AAA(int n) : num(n) { }
}

위와 같이 정의된 클래스가 있으면,

AAA obj = 3; 은 AAA obj(3); 으로 변환된다. 이와같은 경우에 explicit 키워드를 써서 명시적 호출만 허용하면, AAA obj(3); 와 같은 방법으로 객체를 생성해야 한다. 

2] 복사 생성자의 호출 시점 
1.  메모리 공간의 할당과 초기화가 동시에 일어나는 상황
: 복사 생성자의 호출시기를 논하기에 앞서, 먼저 메모리 공간이 할당과 동시에 초기화되는 상황을 나열해 보자. 

int num1 = num2;

위 코드에서 num1 이라는 메모리 공간을 할당해서 num2의 값으로 초기화 시키고 있다. 

int SimpleFunc(int n)

    ...
    return n;
}

int num = 10;
cout<<SimpleFunc(num)<<endl; 

위 코드에서 SimpleFunc의 매개변수 int n은 SimpleFunc 호출 시점에 메모리 공간이 할당됨과 동시에 num 변수의 값인 10으로 초기화 된다. 

그리고! return n; 문장에서도 변수 n의 값을 return 하기위한 임시 메모리 공간이 할당됨과 동시에 n 변수의 값으로 초기화 된다.
?? 반환하는데 메모리 공간이 할당된다? 조금 이해가 안갈 수도 있다. 
하지만, cout<<SimpleFunc(num)<<endl; 문장이 실행되는 과정을 생각해보면 이해할 수 있을것이다.
SimpleFunc 함수에 의해서 반환되는 값을 메모리 공간의 어딘가에 저장해 놓지 않았다면, cout에 의해 출력이 가능하겠는가? 
값이 출력되기 위해서는 그 값을 참조할 수 있어야 하고, 참조가 가능 하려면 메모리 공간의 어딘가에 저장되어야 한다. 

객체의 경우도 마찬가지다. 

SoSimple SimpleFuncObj(SoSimple ob)
{
    ...
    return ob;
}
    
int main(void)
{
    SoSimple obj;
    SimpleFuncObj(obj);
    
    return 0;
}

위의 코드에서도 SimpleFuncOb 함수가 호출되는 순간, 매개변수 ob의 객체 생성과 동시에 초기화가 일어나고 return ob; 에 의해서도 임시 객체 생성과 동시에 초기화가 일어난다. 

2. 할당 이후, 복사 생성자를 통한 초기화
: 위에서 할당과 동시에 초기화가 되는 세가지 시점을 살펴보았다. 
- 객체 생성과 동시에 초기화
- 함수 전달시 매개 변수 객체 생성과 동시에 초기화
- 함수에서 return시 반환을 위한 임시 객체 생성과 동시에 초기화

위와 같은 상황에서 SoSimple 클래스 인스턴스에 대한 메모리가 생성되고 SoSimple 인스턴스를 매개 변수로 받아 복사생성자가 호출되면서 해당 인스턴스가 초기화 된다. 
즉,  SimpleFuncObj() 함수를 한번 호출하면 매개변수 ob의 복사생성자가 호출되고, return 문에 의한 임시 객체에 대한 메모리가 할당되고 그 임시 객체의 복사생성자가 호출된다. 

* 다만, SoSimple tempRef = SimpleFuncObj(obj) 호출시 추가적으로 tempRef 객체가 생성되고 tempRef에 대한 생성자 호출이 일어나지 않는다. SimpleFuncObj 함수의 return 문에 의한 임시객체가 생성되고 그 임시객체를 반환하지 않고 tempRef가 참조하게 만듦으로써, 객체의 생성 수를 하나 줄여준다. 

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

[C++] 상속  (0) 2017.12.25
[C++] Friend와 Static 그리고 Const  (0) 2017.12.25
[C++] 클래스의 완성  (1) 2017.12.23
[C++] 클래스의 기본  (0) 2017.12.23
[C++] C언어 기반의 C++(2)  (1) 2017.12.19
Posted by 홍성곤
,