wn42
코딩이랑 이것저것
wn42
전체 방문자
오늘
어제
  • 분류 전체보기 (113)
    • 프로그래머스 (23)
      • LV1 (11)
      • LV2 (1)
      • LV3 (3)
      • 연습 (8)
    • 딥러닝 공부 (0)
      • 머신러닝&딥러닝 이론 (0)
    • 임베디드 (17)
      • Adventure Design (1)
      • 센서기반모바일로봇 (5)
      • ROS (9)
      • Google Coral (2)
    • C++ (38)
      • C++ 기초 (34)
      • 자료구조 및 알고리즘 (4)
    • Python (14)
      • 기본 파이썬 문법 (6)
      • Python 기초 (8)
    • 빅데이터 (9)
      • 빅데이터 첫걸음 시작하기(국비지원) (5)
      • 빅데이터 공부 (4)
    • 알고리즘 공부 (2)
      • 기본 알고리즘 (2)
    • 전자공학 (10)
      • 반도체 공정 (3)
      • 무선데이터통신 (7)
      • 반도체공학 (0)
    • C# (0)
      • C# 기본 (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 바이트디그리
  • stl
  • K디지털크레딧
  • 패스트캠퍼스
  • 반복문
  • 스택
  • 스택/큐
  • 파이썬
  • ROS
  • 상속
  • 조건문
  • 빅데이터 첫걸음 시작하기
  • numpy
  • 노드
  • 큐
  • google coral
  • 딥러닝
  • 빅데이터
  • c++
  • Queue
  • 데이터분석 인강
  • 변수
  • 소멸자
  • 정렬
  • 프로그래머스
  • 내일배움카드
  • 인스턴스
  • Python
  • 클래스
  • 데이터분석

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
wn42

코딩이랑 이것저것

C++/C++ 기초

[C++] 클래스 상속 (Inheritance)

2023. 1. 15. 18:51

 상속 (inheritance) 

상위 클래스의 내용(속성, 메소드)을 하위 클래스가 그대로 물려받는 것

  • 상위 클래스(super class) == 부모 클래스(parent class) == 기초 클래스(base class)
  • 하위 클래스(sub class) == 자식 클래스(child class) == 파생 클래스(derived class)

 

상속 구현하기

  • 상속 선언 문법은 다음과 같다.
// 단일 상속
class 파생클래스: 접근제어지시자 기초클래스{
	// 파생클래스 멤버
}

// 다중 상속
class 파생클래스: 접근제어지시자 기초클래스, 접근제어지시자 기초클래스{
	// 파생클래스 멤버
}

// EX
class Derived : public Base {
	// members of Derived class
}
  • 접근제어지시자가 public인 경우, 상위클래스의 멤버들의 접근제어지시자 그대로 파생클래스로 상속된다.
  • 접근제어지시자가 protected인 경우, 상위클래스의 public 멤버들은 protected 상태로 바뀌어 파생클래스로 상속된다.
  • 접근제어지시자가 private인 경우, 상위클래스의 모든 멤버들은 private한 상태로 바뀌어 파생클래스로 상속된다.
  • 다중 상속은 어떤 경우에는 위력적인 성능을 보이지만, 여러 새로운 문제를 발생시킬 가능성이 높다. 따라서 될 수 있으면 사용을 자제하는 것이 좋다. (아래 링크의 하단부를 참고)
 

씹어먹는 C++ - <6 - 3. 가상함수와 상속에 관련한 잡다한 내용들>

 

modoocode.com

 

상속 구현

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

class Base {
private:
	string str;

public:
	Base() : str("기초클래스") { cout << "기초 클래스의 생성자가 호출됨" << endl; }
	void printStr() { cout << str << endl; }
};

class Derived : public Base{
private:
	string str;

public:
	Derived() : Base(), str("파생클래스") { 
		cout << "파생 클래스의 생성자가 호출됨" << endl; 
		printStr();	// 생성자 호출하면서 함수도 호출
	}
};

int main() {
	cout << "===== 기초 클래스 생성 =====" << endl;
	Base b;
	
	cout << "===== 파생 클래스 생성 =====" << endl;
	Derived d;

	return 0;
}
===== 기초 클래스 생성 =====
기초 클래스의 생성자가 호출됨
===== 파생 클래스 생성 =====
기초 클래스의 생성자가 호출됨
파생 클래스의 생성자가 호출됨
기초클래스
  • Derived 클래스는 Base 클래스를 상속받는다.
  • Derived 클래스의 생성자 부분을 보면, Derived 클래스는 printStr() 함수를 Base 클래스에서 물려받아 호출한다.
  • Base 클래스에서 printStr() 함수를 그대로 상속받았기 때문에 Derived 클래스의 str이 아닌 Base 클래스의 str이 출력되었다.

 

protected

  • 파생 클래스는 기초 클래스의 private 멤버에 접근할 수 없다.
  • 다행히도 C++은 private와 public의 중간 정도의 성격을 갖는 protected 접근 지시자를 지원한다. 이를 사용하여 멤버를 선언하면 파생 클래스에서도 기초 클래스의 멤버에 접근할 수 있다.

 

파생클래스로 기초클래스의 private 멤버에 접근하기

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

class Base {
private:
	string baseStr;

public:
	Base() : baseStr("기초클래스") { cout << "기초 클래스의 생성자가 호출됨" << endl; }
	void printStr() { cout << baseStr << endl; }
};

class Derived : public Base{
private:
	string derivedStr;

public:
	Derived() : Base(), derivedStr("파생클래스") { 
		cout << "파생 클래스의 생성자가 호출됨" << endl;
		
		// 기초 클래스의 private 멤버에 접근하기
		cout << baseStr << endl;
	}
};

int main() {
	cout << "===== 기초 클래스 생성 =====" << endl;
	Base b;
	
	cout << "===== 파생 클래스 생성 =====" << endl;
	Derived d;

	return 0;
}

  • 위 오류처럼 파생 클래스는 기초 클래스의 private 멤버에 접근할 수 없다.

 

파생클래스로 기초클래스의 protected 멤버에 접근하기

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

class Base {
protected:	// protected로 선언
	string baseStr;

public:
	Base() : baseStr("기초클래스") { cout << "기초 클래스의 생성자가 호출됨" << endl; }
	void printStr() { cout << baseStr << endl; }
};

class Derived : public Base{
private:
	string derivedStr;

public:
	Derived() : Base(), derivedStr("파생클래스") { 
		cout << "파생 클래스의 생성자가 호출됨" << endl;
		
		// 기초 클래스의 private 멤버에 접근하기
		cout << baseStr << endl;
	}
};

int main() {
	cout << "===== 기초 클래스 생성 =====" << endl;
	Base b;
	
	cout << "===== 파생 클래스 생성 =====" << endl;
	Derived d;

	return 0;
}
===== 기초 클래스 생성 =====
기초 클래스의 생성자가 호출됨
===== 파생 클래스 생성 =====
기초 클래스의 생성자가 호출됨
파생 클래스의 생성자가 호출됨
기초클래스
  • protected 키워드를 이용하면 파생클래스에서도 기초클래스 멤버에 접근할 수 있다.

 

 

오버라이딩

  • 오버라이딩(Overriding)은 같은 이름의 함수를 새롭게 정의하는 것이다.
  • 파생 클래스는 상속받은 함수를 그대로 사용해도 되고, 다시 이를 재정의하여 사용할 수 있다.
  • 멤버 함수를 오버라이딩 하는 방법 2가지
    • 1. 파생 클래스에서 직접 오버라이딩
    • 2. 가상 함수를 이용하여 오버라이딩

 

파생 클래스에서 오버라이딩하기

class Derived : public Base{
private:
	string str;

public:
	Derived() : Base(), str("파생클래스") { 
		cout << "파생 클래스의 생성자가 호출됨" << endl; 
		printStr();	// 생성자 호출하면서 함수도 호출
	}
	void printStr() { cout << str << endl; }	// 파생 클래스에서 재정의
};
  • 위의 예제를 다시 이용하여 위처럼 파생 클래스에서 함수를 재정의한다.
  • 이렇게 파생 클래스에서 함수를 다시 정의하면, 컴파일러는 기초 클래스의 멤버 함수 대신 파생 클래스의 멤버 함수를 호출한다. 결과는 다음과 같다.
===== 기초 클래스 생성 =====
기초 클래스의 생성자가 호출됨
===== 파생 클래스 생성 =====
기초 클래스의 생성자가 호출됨
파생 클래스의 생성자가 호출됨
파생클래스

 

파생 클래스 오버라이딩의 문제점

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

class Base {
private:
	string str;

public:
	Base(const string& _str) { str = _str; }
	void printStr() { cout << "기초클래스: " << str << endl; }
};

class Derived : public Base {
private:
	int num;

public:
	Derived(const string& _str, int _num) : Base(_str) { num = _num; }
	void printStr() { cout << "파생클래스: " << num << endl; }	// str 대신 num 출력하도록 오버라이딩
};

int main() {
	Base* ptr;
	Base a("base class");
	Derived b("derived class", 10);

	ptr = &a;
	ptr->printStr();
	ptr = &b;	// Derived는 Base를 상속받기 때문에 is-a 관계. 따라서 Base 객체 포인터는 Derived 객체를 가리킬 수 있음
	ptr->printStr();	// 이를 업 캐스팅(파생 클래스에서 기반 클래스로 캐스팅하는 것)이라한다.

	return 0;
}
기초클래스: base class
기초클래스: derived class
  • 파생 클래스에서 직접 오버라이딩할 때의 문제점은 포인터를 사용할 때 나타난다.
  • 위 코드에서 ptr은 Base 클래스 타입의 포인터로 정의되어 있다.
  • ptr이 가리키는 객체를 변경하여도 컴파일러는 포인터 ptr이 가리키는 변수의 타입을 기준으로 함수를 호출한다.
  • 그렇기 때문에 파생 클래스에서 정의된 함수가 아닌, 기초 클래스의 멤버 함수가 호출되었다.
  • 이러한 문제점을 해결하기 위해 C++에서는 virtual 키워드를 제공한다.

 

가상 함수를 이용하여 오버라이딩

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

class Base {
private:
	string str;

public:
	Base(const string& _str) { str = _str; }
	virtual void printStr() { cout << "기초클래스: " << str << endl; }
};

class Derived : public Base {
private:
	int num;

public:
	Derived(const string& _str, int _num) : Base(_str) { num = _num; }
	virtual void printStr() { cout << "파생클래스: " << num << endl; }
    // 기초 클래스에서만 virtual 키워드를 사용해도 되지만, 이왕이면 파생 클래스에서도 
    // virtual 키워드를 사용해서 해당 함수가 가상함수임을 명확하게 드러내는 편이 좋다.
};

int main() {
	Base* ptr;
	Base a("base class");
	Derived b("derived class", 10);

	ptr = &a;
	ptr->printStr();
	ptr = &b;
	ptr->printStr();

	return 0;
}
기초클래스: base class
파생클래스: 10
  • virtual을 함수 원형 앞에 붙이기만 하면 된다.
  • virtual 키워드를 통해 가상함수로 선언하면, 컴퓨터는 포인터가 실제로 가리키는 객체에 따라 멤버함수를 호출한다.
  • 어떤 함수가 실행될지 컴파일러는 알지 못하고(컴파일 때는 모르고), 런타임 때 포인터가 가리키는 객체를 확인하여 알맞은 함수를 호출한다. 컴파일 때가 아닌 런타임 때 동작이 정해지는 것을 동적 바인딩(dynamic binding)이라고 한다.
  • 가상함수를 생성하면 가상 함수 테이블(virtual function table, vtbl)가 같이 작성된다. C++ 프로그램은 가상함수가 호출되면 가상 함수 테이블에 접근하여 자신이 필요한 함수의 주소를 찾아서 호출한다. 이때 약간의 오버헤드(메모리, 실행 속도 측면)가 발생하므로, C++에서는 virtual 함수를 Default로 두지는 않는다. (Java는 모든 함수가 virtual)

 

override 키워드

void printStr() override { cout << "파생클래스: " << num << endl; }
  • C++11부터는 파생클래스에 override 키워드를 명시하여 이것이 오버라이딩되는 함수임을 나타낼 수 있다.
  • override 키워드를 사용하면, 실수로 오버라이딩을 하지 않는 경우를 막을 수 있다.

 

레퍼런스로 접근하기

#include <iostream>
using namespace std;

class Base {
public:
	virtual void show() { cout << "기초클래스" << endl; }
};

class Derived : public Base {
public:
	void show() override { cout << "파생클래스" << endl; }
};

void test(Base& a) { a.show(); }	// 레퍼런스 인자
int main() {
	Base a;
	Derived b;
	test(a);
	test(b);
	return 0;
}
기초클래스
파생클래스
  • 레퍼런스를 인자로 받는 함수로도 오버라이딩된 함수를 호출할 수 있다.
  • 레퍼런스 인자의 타입이 Base이지만, virtual로 정의되어 있으므로 Derived 객체의 함수가 호출된다.
    'C++/C++ 기초' 카테고리의 다른 글
    • [C++] 순수 가상 함수 / 추상 클래스
    • [C++] virtual 소멸자
    • [C++] friend 키워드
    • [C++] 오버로딩 (Overloading) - 2. 연산자
    wn42
    wn42
    코딩이랑 이것저것 하는 블로그

    티스토리툴바