반응형

 

업캐스팅 (UpCasting)

- C++에서, A 클래스 형 포인터 변수는 A 클래스의 객체 또는 A 클래스를 직, 간접적으로 상속하는 모든 객체를 가리킬 수 있다. (자식 클래스 객체의 주소값을 부모 클래스 포인터 변수에 담아 사용할 수 있다.)

- IS-A 상속 관계의 성립으로 인해서, 부모 클래스를 직, 간접적으로 상속하는 객체를 부모 클래스 객체의 일종으로 간주한다.

- 여러 자식 클래스들의 부모 클래스가 동일 할 경우 해당 부모 클래스에 여러 자식 클래스를 일괄적으로 처리가 가능하다.

#include <iostream>
using namespace std;

class Parent
{
public:
    void ShowParent()
    {
        cout << "Parent class" << endl;
    }
};

class Son : public Parent
{
public:
    void ShowSon()
    {
        cout << "Son class" << endl;
    }
};

class Daughter : public Parent
{
public:
    void ShowDaughter()
    {
        cout << "Daughter class" << endl;
    }
};

void main()
{
    Parent* ptr[2] = { new Son(), new Daughter() };
    for (int i = 0; i < 2; i++)
    {
        ptr[i]->ShowParent();
    }
    
    //결과
    //Parent class
    //Parent class
}

 

 

※ 업캐스팅 사용시 주의사항

- 위와 같이 업캐스팅하여 함수를 호출할 때 다음과 같은 경우는 컴파일 에러를 일으킨다.

ptr[0]->ShowSon();
ptr[1]->ShowDaughter();

 

Why?

- 포인터의 형이 존재하는 이유는 메모리를 참조하는 기준이 된다. (C에서 포인터를 했을때 확인했던 개념이다.)

- 즉, Son, Daughter의 객체를 참조하는 ptr은 Person의 메모리 크기만을 참조하기 때문이다.

 

 

 

 

오버라이딩(Overriding)

- 부모 클래스의 함수를 자식 클래스가 동일한 이름으로 만들어 해당 함수의 내용을 재정의한다.

#include <iostream>
using namespace std;

class Parent
{
public:
    void SayHello()
    {
        cout << "Parent : 'Hello'" << endl;
    }
};

class Son : public Parent
{
public:
    void SayHello()
    {
        cout << "Son : 'Hello'" << endl;
    }
};

void main()
{
    Parent parent;
    Son son;
    
    parent.SayHello();
    son.SayHello();
    
    //결과
    //Parent : 'Hello'
    //Son : 'Hello'
}
  • 위의 코드를 기준으로 자식 클래스에서 부모 클래스의 함수를 오버라이딩 했다고 해서 업캐스팅으로 자식 클래스의 객체를 참조했을때 오버라이딩 된 함수가 호출되지 않는다.
  • 이유는 업캐스팅 주의사항에서 언급했던 것과 동일하다. 그리고 이러한 문제를 해결해주는게 가상(virtual)함수이다.

 

 

 

 

가상함수(Virtual Function)

- 자식 클래스를 부모 포인터에 업캐스팅 했을 시, 오버라이딩된 함수를 사용하게 해주는 방법

- 부모 클래스에 virtual을 사용해준다.

  • virtual 반환형 함수명 (매개변수)

- 소멸자 함수도 virtual을 사용해 줘야한다. (사용하지 않으면 자식 클래스의 소멸자가 호출되지 않는다.)

#include <iostream>
using namespace std;

class Parent
{
public:
    virtual void SayHello()
    {
        cout << "Parent : 'Hello'" << endl;
    }
    void SayBye()
    {
        cout << "Parent : 'Bye'" << endl;
    }
    virtual ~Parent()
    {
        cout << "Parent Destructor" << endl;
    }
};

class Son : public Parent
{
public:
    void SayHello()
    {
        cout << "Son : 'Hello'" << endl;
    }
    void SayBye()
    {
        cout << "Son : 'Bye'" << endl;
    }
    ~Son()
    {
        cout << "Son Destructor" << endl;
    }
};

void main()
{
    Parent* ptr = new Son();
	
    ptr->SayHello();
    ptr->SayBye();
    delete ptr;
	
    //결과
    //Son : 'Hello'
    //Parent : 'Bye'
    //Son Destructor
    //Parent Destructor
}
  • 동적할당을 해제할 때 부모 클래스의 virtual 유무에 따라 자식 클래스의 소멸자가 호출되는지 확인해보는것도 좋다.

 

※※ 추가 : 멤버, 가상함수의 동작원리

 

C++ 멤버함수, 가상함수 동작원리

멤버함수 동작원리 - C++의 객체의 멤버변수는 객체 내에 존재하지만 멤버함수는 다음과 같은 관계를 갖는다. 멤버함수는 메모리의 한 공간에 별도로 위치한다. 멤버함수가 정의된 클래스의 모

srdeveloper.tistory.com

 

 

반응형

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

C++ 멤버함수, 가상함수 동작원리  (0) 2021.11.17
C++ 순수 가상함수, 추상 클래스  (0) 2021.11.11
C++ 클래스 상속  (0) 2021.11.04
C++ 함수 오버로딩, Deault 매개변수  (0) 2021.10.24
C++ this 포인터  (0) 2021.10.24
반응형

 

상속(Inheritance)

- 다른 클래스의 멤버 변수, 멤버 함수를 자신의 멤버인 것처럼 사용이 가능.

- B라는 클래스가 A 클래스를 상속하게 되면, B 클래스는 A 클래스가 지니고 있는 모든 멤버를 물려받는다.

- 클래스의 재사용과 관련이 있다.

 

상속 예제 코드

#include <iostream>
#include <string>
using namespace std;

class Person
{
private:
    int age;
    int height;
public:
    Person(int _age, int _height) : age(_age), height(_height) { }
};

class Employee : public Person
{
private:
    string department;
    string position;
public:
    Employee(string _department, string _position, int _age, int _height) : Person(_age, _height)
    {
        department = _department;
        position = _position;
    }
};

void main()
{
    Employee employee("R&D", "Staff", 20, 180);
}
  • Person 클래스를 상속한 Employee 클래스의 생성자는 부모 클래스인 Person 클래스의 멤버까지 초기화해야할 의무가 있다.
  • 자식 클래스의 생성자는 부모 클래스의 생성자를 호출해서 부모 클래스의 멤버를 초기화하는 것이 좋다.
  • (자식 클래스는 이니셜라이저(콜론 초기화)를 이용해서 상속 클래스의 생성자 호출을 명시할 수 있다.)

 

상속 용어 정리

Person Employee
상위 클래스 하위 클래스
기초(Base) 클래스 유도(Derived) 클래스
슈퍼(Super) 클래스 서브(Sub) 클래스
부모 클래스 자식 클래스
  • 일반적으로 기초 클래스와 유도 클래스라는 표현을 사용한다.

 

 

 

상속 생성자 호출 순서 예제 코드

#include <iostream>
using namespace std;

class Base
{
private:
    int baseNum;
public:
    Base(int n) : baseNum(n)
    {
        cout << "Base(int n)" << endl;
    }
    ~Base()
    {
        cout << "~Base()" << endl;
    }
};

class Derived : public Base
{
private:
    int derivNum;
public:
    Derived(int n1, int n2) : Base(n1), derivNum(n2)
    {
        cout << "Derived(int n1, int n2)" << endl;
    }
    ~Derived()
    {
        cout << "~Derived()" << endl;
    }
};

void main()
{
    Derived derived(10, 20);
    
    //결과
    //Base(int n)
    //Derived(int n1, int n2)
    //~Derived()
    //~Base()
}
  • 자식 클래스에서 생성자가 호출될 때, 이니셜라이저에 부모 클래스의 생성자가 호출이 되는지 확인한 후, 이니셜라이저에 있는 생성자가 호출될지 void형 생성자가 호출될지 정해진다.
  • 자식 클래스를 만든다고 해서 자식 클래스의 생성자가 먼저 호출되지 않고, 부모 클래스의 생성자가 먼저 호출 되는것을 확인할 수 있다.
  • 소멸자는 생성자와 반대로 자식 클래스, 부모 클래스 순으로 호출된다.

 

 

 

상속과 접근지시자

- public > protected > private

- 상속 관계에서는 public과 protected가 차이를 보이지 않는다.

- protected에 선언된 멤버는 자식 클래스에서도 자유롭게 사용할 수 있지만, 부모 클래스와 자식 클래스 사이에서도 '정보은닉'은 지켜지는게 좋다.

- 상속을 명시할 때 사용하는 접근지시자는 다음과 같은 특성을 가지고 있다.

  • public : public 범위의 멤버를 public으로 변경시켜서 상속.
  • protected : protected 보다 넓은(public) 접근 범위의 멤버를 protected로 변경시켜서 상속
  • private : private 보다 넓은(public, protected) 접근 범위의 범버를 private로 변경시켜서 상속

- public 이외의 상속은 다중상속과 같이 특별한 경우가 아니라면 잘 사용하지 않는다.

 

 

 

 

상속의 조건

- 상속은 다음과 같은 조건에서 사용한다.

  • IS-A((자식)은 (부모)이다) : Employee(자식 클래스)는 Person(부모 클래스)이다. 
  • HAS-A((자식)은 (부모)를 갖고있다) : Police(자식 클래스)는 Gun(부모 클래스)를 갖고있다.

- HAS-A의 관계는 다음과 같은 이유로 상속보다는 멤버로 갖고있는것이 더 좋은 모델이 될 수 있다.

  • 총을 소유하지 않은 경찰을 표현해야 할 수도 있다.
  • 경찰이 총이 아니라, 3단봉을 소유할 수도 있다.

- 결론 : 상속은 IS-A 관계의 표현에 적합. HAS-A 관계에서도 사용될 수 있으나, 프로그램 변경에 많은 제약을 가져다 줄 수 있다.

반응형

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

C++ 순수 가상함수, 추상 클래스  (0) 2021.11.11
C++ 업캐스팅, 오버라이딩, 가상함수  (0) 2021.11.08
C++ 함수 오버로딩, Deault 매개변수  (0) 2021.10.24
C++ this 포인터  (0) 2021.10.24
C++ 복사 생성자 심화  (0) 2021.10.17
반응형

 

함수 오버로딩(Function overloading)

- C에서는 동일한 이름의 함수가 정의되는것을 허용하지 않는다.

- C++에서는 함수에 전달되는 인자를 통해서 호출하려는 함수의 구분이 가능하다.

- 그렇기 때문에 매개변수의 선언형태가 다르다면 동일한 이름의 함수를 정의할 수 있다.

  ※ C에서는 함수의 이름만 이용해서 호출되는 대상을 찾는 반면, C++에서는 함수의 이름과 매개변수 선언 두 가지 정보를 이용해 호출 대상을 찾는다.

#include <iostream>
using namespace std;

void Func(int n)
{
    cout << n << endl;
}

void Func(char c)
{
    cout << c << endl;
}

void main()
{
    Func(10);
    Func('A');
    
    //결과
    //10
    //A
}

 

 

 

 

Default 매개변수

- 단어 그대로 기본적으로 설정되어 있는 매개변수라고 생각하면 된다.

- 함수를 호출했을 때, 매개변수 값을 전달하지 않으면 자동으로 기본값을 가진다.

- Default 값은 함수의 원형에만 위치시켜야한다.

#include <iostream>
using namespace std;

class Test
{
public:
    void Func(int n1 = 10, int n2 = 20);
};

void Test::Func(int n1, int n2)
{
    cout << n1 + n2 << endl;
}

void main()
{
    Test test;
    test.Func();
    
    //결과 30
}

 

 

부분적 Defualt 값 설정

- 반드시 모든 매개변수가 Default 값을 가질 필요는 없다.

- 다음과 같은 경우에 부분적 Default 값을 저장할 수 있다.

  • 반드시 오른쪽 매개변수의 Default 값부터 채우는 형태로 정의한다.
void Func(int n1, int n2 = 20, int n3 = 10)
  • Default 매개변수로 인해 형성되는 모호한 오버로딩은 허용하지 않는다.
#include <iostream>
using namespace std;

class Test
{
public:
    void Func();
    void Func(int n1 = 10, int n2 = 20);
};

void Test::Func()
{
    cout << 100 << endl;
}

void Test::Func(int n1, int n2)
{
    cout << n1 + n2 << endl;
}

void main()
{
    Test test;
    test.Func(); //에러
}
반응형

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

C++ 업캐스팅, 오버라이딩, 가상함수  (0) 2021.11.08
C++ 클래스 상속  (0) 2021.11.04
C++ this 포인터  (0) 2021.10.24
C++ 복사 생성자 심화  (0) 2021.10.17
C++ 파일 입출력 ofstream, ifstream  (0) 2021.10.16
반응형

 

this 포인터

- 자신을 가리키는 포인터이다.

- 객체의 0번째 멤버변수라고도 한다.

- 멤버함수에서 멤버변수에 접근하기 위해 사용한다.

class Test
{
private:
    int num1, num2;
public:
    void Add(int num1, int nm2)
    {
        //num1과 num2는 전부 지역변수이다.
        num1 = num1;
        num2 = num2;
        
        //그렇기 때문에 this 포인터로 멤버변수를 명시해준다.
        this->num1 = num1;
        this->num2 = num2;
    }
};
  • 변수명이 동일하면 제일 가까운 위치에 있는 변수로 취급된다.
  • 그렇기 때문에 this 포인터로 멤버변수를 구분해준다.
반응형

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

C++ 클래스 상속  (0) 2021.11.04
C++ 함수 오버로딩, Deault 매개변수  (0) 2021.10.24
C++ 복사 생성자 심화  (0) 2021.10.17
C++ 파일 입출력 ofstream, ifstream  (0) 2021.10.16
C++ 생성자, 복사생성자, 소멸자 (+ const)  (0) 2021.10.15
반응형

 

※ 들어가기에 앞서 참조자와 복사 생성자에 대한 사전 지식이 필요하니 다음 글을 참조

 

C++ 생성자, 소멸자 (+ const)

들어가기에 앞서... const - const 변수 : 해당 변수를 정보 변경이 불가능한 상수로 변환. - const 함수 : 해당 함수에서 const의 위치에 따라 정보의 변환을 막거나 반환값을 상수로 바꾼다. 1. const 변수

srdeveloper.tistory.com

 

C++ 참조자(Reference)

참조자 (Reference) - 할당된 어떤 하나의 메모리 공간에 다른 이름을 부여하는 것이다. ※ 별명이라고 생각해도 좋다. - 참조자는 변수 앞에 &를 붙여줌으로써 선언할 수 있다. ※ 이미 선언된 변수

srdeveloper.tistory.com


깊은 복사, 얕은 복사

- 깊은 복사 : 값이 복사되는 복사

- 얕은 복사 : 참조 값이 복사되는 복사

 

 

 

디폴트 복사 생성자의 문제점

- 앞서 생성자, 소멸자 포스팅에서 복사 생성자는 정의하지 않아도, 디폴트 복사 생성자가 자동으로 삽입된다.

- 하지만, 반드시 복사 생성자를 정의해야 하는 경우가 있다.

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;

class Test
{
private:
	
    char* cArr;
public:
    Test(const char* cArr)
    {
        int len = strlen(cArr) + 1;
        this->cArr = new char[len];
        strcpy(this->cArr, cArr);
    }
    void Show()
    {
        cout << "cArr : " << cArr << endl;
    }
    ~Test()
    {
        delete cArr;
        cout << "Called destructor" << endl;
    }
};

void main()
{
    Test t1("Test string");
    Test t2 = t1;
    t1.Show();
    t2.Show();
    
    //결과
    //cArr : Test string
    //cArr : Test string
    //Called destructor
}
  • 위와 같이 char 포인터를 디폴트 복사 생성자로 복사가 일어나게 되면 얕은 복사가 일어난다.
  • 즉, 참조값이 복사가 되기 때문에 t2객체와 t1객체의 cArr 포인터가 참조하는 문자열은 같다고 볼 수 있다.
  • 그렇기 때문에 어떤 객체의 소멸자에서 cArr이 참조하는 문자열을 delete로 할당해제 하면 남은 객체에서는 이미 지워진 문자열을 대상으로 delete 연산을 하기 때문에 문제가 된다.
  • 따라서 복사 생성자를 정의할때에는 이러한 문제가 발생하지 않도록 신경써야한다.

 

 

 

복사 생성자의 호출 시점

- 복사 생성자의 호출 시점은 3가지로 구분된다.

  • 기존에 생성된 객체를 이용해서 새로운 객체를 초기화할 때
SomeClass sc1;
SomeClass sc2 = sc1;
  • Call by value 함수호출 과정에서 객체를 인자로 전달할 때
SomeClass Func(SomeClass sc)
{
    ...
}

void main()
{
    SomeClass sc;
    Func(sc)	// 호출하게 되면 매개변수 sc가 할당되는 동시에 초기화
}
  • 함수에서 객체를 참조형으로 반환하지 않을 때
SomeClass Func(SomeClass sc)
{
    return sc	// 반환할 때 메모리 공간이 할당되면서 동시에 초기화
}

void main()
{
    SomeClass sc1;
    SomeClass sc2 = Func(sc1);
}

  ※ 즉, 위 코드에서 반환되는 sc의 객체는 인수로 받은 sc가 아니라 '임시객체'가 만들어져 이 '임시객체'에서 복사 생성자가 호출되어 return에 명시된 객체가 인자로 전달된다.

 

 

 

임시객체, 소멸시점

- 클래스 외부에서 객체의 멤버함수에 접근하는 방법은 다음 세가지이다.

  • 객체에 붙여진 이름
  • 객체의 참조 값 (임시객체)
  • 객체의 주소 값

- 임시객체가 생성된 위치에는 임시객체의 참조 값이 반환된다.

- 그렇기 때문에 다음과 같은 구성이 가능하다.

#include <iostream>
using namespace std;

class Test
{
private:
    int n;
public:
    Test(int n)
    {
        cout << "Create" << endl;
        this->n = n;
    }
    void Show()
    {
        cout << n << endl;
    }
    ~Test()
    {
    	cout << "Destroy" << endl;
    }
};

void main()
{
    Test(100).Show();
    cout << "-----" << endl;
    const Test& t = Test(200);
    cout << "-----" << endl;
    
    //결과
    //Create
    //100
    //Destroy
    //-----
    //Create
    //-----
    //Destroy
}
  • 참조자는 '임시객체'에 대한 참조가 가능하기 때문에 위와 같이 사용이 가능하다.
  • 이 임시객체는 반환된 뒤 다음 줄로 넘어가면 자동으로 소멸 되지만, '임시객체'에 대한 참조자가 존재하면 바로 소멸되지 않는다.

※ 복사 생성자가 일어나지 않는 예외

SomeClass Func()
{
    return SomeClass
}

void main()
{
    SomeClass sc = Func();
}
  • 위의 코드에서 sc 객체를 생성해서, Func()에서 반환되는 객체를 가지고 대입연산을 진행하는것으로 이해할수도 있지만 위와 같은 상황에서는 객체를 생성하지 않고 반환되는 임시객체에 sc라는 이름을 할당한다고 보면 된다. (객체의 생성 수를 하나 줄여서 효율성을 높이기 위해서이다.)
반응형

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

C++ 함수 오버로딩, Deault 매개변수  (0) 2021.10.24
C++ this 포인터  (0) 2021.10.24
C++ 파일 입출력 ofstream, ifstream  (0) 2021.10.16
C++ 생성자, 복사생성자, 소멸자 (+ const)  (0) 2021.10.15
C++ 동적할당 new, delete  (0) 2021.10.13

+ Recent posts