C++ Inheritance

이정빈·2023년 6월 1일
0

1. Class Inheritance

상속과 복합을 구분 잘하자.
상속은 is-a 관계, 복합은 has-a 관계임을 잊지말자.

1.1 접근 지정자

class Base {
private:
	int x;
protected:
	int y;
public:
	int z;
};

class privateD :private Base {
	// z는 private;
	// y는 privtae
	// x는 직접 접근 불가능
};

class protectedD :protected Base {
	// z는 protected
	// y는 protected
	// x는 직접 접근 불가능
};

class publicD :public Base {
	// z 접근 가능
	// y 접근 가능
	// x 직접 접근 불가능
};

생략 시 private 상속으로 된다.

public 상속 : protected, public 멤버들은 접근 지정 변경 없이 그대로 상속
하지만 private는 직접 접근이 안된다.

protected 상속 : protected, public 멤버들은 모두 protected로 접근 지정 변경된다.
하지만 여전히 private는 직접 접근이 안된다.

private 상속 : protected, public 멤버들은 모두 private 접근 지정으로 변경된다.

아무리 private로 받아도 접근 지정이 바껴서 영향을 끼치는 위치는 파생의 파생 클래스이다.
전부 private로 바뀌기 때문에 직접 접근이 불가능해진다.
즉, 베이스 클래스의 public,protected 멤버들은 private로 받아도 직접 접근 가능하다.

protected 상속도 마찬가지이다. 영향은 파생의 파생클래스에 미친다.

class Base {
private:
	int a;
protected:
	void setA(int a) {
		this->a = a;
	}
public:
	void showA() {
		cout << a;
	}
};
class Derived :private Base {
private:
	int b;
protected:
	void setB(int b) {
		this->b = b;
	}
public:
	void showB() {
		setA(5);
		showA();
		cout << b;
	}
};
class Grand :private Derived {
private:
	int c;
protected:
	void setAB(int x) {
		setA(x);
		showA();
		showB();
	}
};

grand 클래스의 setA,showA()는 오류가 남!

그리고 main에서 사용하는 것과 파생클래스에서 사용하는 것과 느낌이 비슷함.
but, 조금 더 까다로운 조건

class TmpBase {
private:
	int a = 1;
public:
	TmpBase(int x) :a(x) {}
};

class TmpDr : public TmpBase {
private:
	int b;
public:
	TmpDr(int x, int y)
		:TmpBase(x), b(y) {}
	int getA() const {
		return a;
	}
	int getB() const {
		return b;
	}
};

private멤버는 상속받으면 직접 접근이 불가능하므로 protected나 우회 함수로 사용해야 한다.

class TmpBase {
protected:
	int a = 1;
public:
	TmpBase(int x) :a(x) {}
	int getA() {
		return a;
	}
};

1.2 생성자 / 소멸자

class TmpBase {
public:
	TmpBase() {
		cout << "1" << endl;
	}
	~TmpBase() {
		cout << "2" << endl;
	}
};

class TmpDr : private TmpBase {
public:
	TmpDr() {
		cout << "3" << endl;
	}
	~TmpDr() {
		cout << "4" << endl;
	}
};

void main() {
	TmpDr d;

}

1
3
4
2

Base클래스의 생성자가 먼저 생성되고 그 후에 파생클래스의 생성자가 생성된다.

class A {
private:
	int a;
public:
	A() = default;
	A(int x) :a(x) {}
	int get() {
		return a;
	}
	void set(int x) {
		a = x;
	}
};

class B :public A {
public:
	B() :A() {}
	B(int a) :A(a) {}
	void show() {
		cout << "show" << endl;
	}
};

void main() {
	B b(5);
	b.set(10);
	b.get();
}

생성자를 상속한 경우이다.복합이 아니라 상속인 것을 잊지 말아야한다. 복합과는 비슷한 부분이 있긴하다. 상속을 받았으므로 B의 객체로 A에 접근 가능하다.

class A {
public:
	A() {
		cout << "a con" << endl;
	}
	virtual ~A() {
		cout << "a des" << endl;
	}
};

class B :public A {
public:
	B() {
		cout << "b con" << endl;
	}
	virtual ~B() {
		cout << "b des" << endl;
	}
};

void main() {
	A* a = new B;
	delete a;
}

a con
b con
b des
a des

가상함수를 사용하면 포인터 타입이 아니라 객체가 생성되는 곳을 따라야 하기때문에 호출결과가 정상적으로 나온다.

만약 가상함수를 사용하지 않으면 따로 파생 클래스를 소멸시켜야한다. 왜냐하면 포인터는 객체가 아닌 반환 타입이 우선이기 때문이다.

1.3 Inheritance Variable Shadowing

변수 가리기는 베이스 클래스의 변수를 가르는 것이다.

class TmpBase {
private:
	int a;
public:
	TmpBase(int a) :a(a) {}
	void display() {
		cout << "Base " << a << endl;
	}
};

class TmpDr :public TmpBase {
private:
	int a;
public:
	TmpDr(int a, int b) :TmpBase(a), a(b) {}
	void display() {
		cout << "der " << a << endl;
	}
};

void main() {
	TmpDr d{ 10,12 };
	d.display();
	d.TmpBase::display();
  	
  	TmpBase::display() // 이것은 오류
}

der 12
Base 10

기초 클래스의 protected 이하의 멤버 함수들을 통해서 접근이 가능하다.
display()도 어떻게 보면 가려진 것이라고 볼 수 있긴 하

굳이 사용할 필요도 없고 헷갈린다. 사용하지 말자

2. Casting

https://blockdmask.tistory.com/241
https://gerrk.tistory.com/50
참고하자

2.1 형변환

  1. dynamic_cast : 상속관계에서의 안전한 형 변환

상속관계에 놓여 있는 두 클래스 사이에서 유도 클래스의 포인터 및 참조형 데이터를 기초 클래스의 포인터 및 참조형 데이터로 형 변환하는 경우 (다운 캐스팅)

부모 클래스의 포인터가 실제로 무엇을 가르키고 있는지가 중요함.
virtual이 사용되었는지도 중요함

Derived* dr = new Derived(10);
Base* b = dynamic_cast<Base*>(dr);

기초클래스의 포인터 데이터가 자기 자식을 가르키는 경우 dynamic_cast사용 가능

  1. static_cast : dynamic개념 + dynamic 개념의 반대

기초클래스의 포인터 및 참조형 데이터도 유도 클래스의 포인터 및 참조형 데이터로 형 변환 시켜줌.

자식 클래스 포인터가 가르키고 있는 클래스(부모 클래스)로 형 변환할때는 업캐스팅 (static_cast 가 사용)

Base* b=new Derived(19);
Derived* dr=static_cast<Derived*>(b); // 원하는 결과가 안 나옴

포인터가 가르키는 곳이랑 객체랑 타입이 같아야함.

  1. const_cast : const의 의미를 제거함.
  2. reinterpret_cast : 상관없는 자료형으로의 형 변환

해당 주소가 가지고 있는 값을 어떤 형 변환을 해도 그대로 보존하고 있다.

2.2 Up,Down Casting

  1. 업 캐스팅

기초 클래스의 포인터 변수로 유도 클래스의 객체를 가라키는 것

class TmpBase {
public:
	void display() {
		cout << "base" << endl;
	}
};

class TmpDr :public TmpBase {
public:
	void display() {
		cout << "derivde" << endl;
	}
};

void main() {
	TmpBase* base = new TmpBase;
	base->display();
	TmpDr* dr = new TmpDr;
	dr->display();
	base = static_cast<TmpBase*>(dr);
	base->display();
}

base
derived
base

파생클래스의 포인터를 기초 클래스의 포인터로 업 캐스팅했다.
dynamic_cast를 해도 됨

  1. 다운 캐스팅

업캐스팅된 포인터 변수를 유도 클래스의 포인터 변수로 형변환 시키는 것

class TmpBase {
public:
	void display() {
		cout << "base" << endl;
	}
	virtual ~TmpBase() {}
};

class TmpDr :public TmpBase {
public:
	void display() {
		cout << "derived" << endl;
	}
};

void main() {
	TmpBase* b = new TmpDr;
	b->display();
	dynamic_cast<TmpDr*>(b)->display();
}

base
derived

가상 함수가 없었다면 오류가 남

왜냐하면, 정적 바인딩으로 결정이 된 상태에서 b가 TmpDr 객체를 가리키고 있더라도 TmpDr 클래스의 멤버 함수를 호출하지 않는다. 이는 컴파일 시간에 이미 TmpBase의 display로 이미 결정이난 상태이다. (dynamic_Cast는 주로 동적 virtual상태에서)

class TmpBase {
public:
	virtual void fun() = 0;
};

class TmpDrA :public TmpBase {
public:
	virtual void fun() {
		cout << "fun of A" << endl;
	}
	void funA() {
		cout << "funA()" << endl;
	}
};
class TmpDrB :public TmpBase {
public:
	virtual void fun() {
		cout << "fun of B" << endl;
	}
	void funB() {
		cout << "funB()" << endl;
	}
};

void main() {
	TmpBase* b = new TmpDrA;
	TmpDrB* dr = static_cast<TmpDrB*>(b);
	dr->fun();
	dr->funB();
}

fun of A
funB()

static_cast를 사용해서 작동이 되었지만 dynamic_cast로는 null ptr이 되어서 오류가 난다.
왜 오류가 나나면, 기초 클래스는 파생 클래스에 대한 정보가 없는데 강제로 형변환 시키므로 오류가 나는 것이다. static_cast는 허용해주지만, dynamic_cast는 아니다.
또한, 서로 상속관계가 아니므로 정보가 없다.
(dynamic은 기초클래스가 적어도 한 개의 가상함수와, 그것을 오버라이드한 파생클래스가 있을 때 가능)

class TmpBase {
public:
	virtual void fun() {};
};

class TmpDrA :public TmpBase {
public:
	virtual void fun() {
		cout << "fun of A" << endl;
	}
	void funA() {
		cout << "funA()" << endl;
	}
};
class TmpDrB :public TmpDrA {
public:
	virtual void fun() {
		cout << "fun of B" << endl;
	}
	void funB() {
		cout << "funB()" << endl;
	}
};

void main() {
	TmpBase* base = new TmpDrA;
	if (TmpDrB* drb = dynamic_cast<TmpDrB*>(base)) {
		cout << 1;
	}
	else {
		cout << 2;
	}
}

2

실제로 TmpBase가 TmpDrB를 가리키고 있지 않기 때문이다.
dynamic_cast로 런타임에 타입 검사를 수행함
성공하려면 base는 TmpDrB를 가르키고 있어야 한다.

Prac 1

class Count {
protected:
	int chicken[3];
	int total;
public:
	void accept_details() {
		cout << endl << "Enter the Number of Each Item to Order ";
		cout << "\n ------------------------\n";
		cout << "\n BBQ Chickn : ";
		cin>> chicken[0];
		cout << "\n Korean Spicy Korean : ";
		cin >> chicken[1];
		cout << "\n Soybean Sauce Chicken : ";
		cin >> chicken[2];
	}
};

class Price :public Count {
protected:
	int prices[3];
public:
	void fix_order() {
		total = 0;
		for (int i = 0; i < 3; i++) {
			total += chicken[i];
		}
		prices[0] = 10;
		prices[1] = 10;
		prices[2] = 10;
	}
};

class Order :public Price {
private:
	int totalPrice;
public:
	void calculate_price() {
		totalPrice = chicken[0] * prices[0];
		totalPrice += chicken[1] * prices[1];
		totalPrice += chicken[2] * prices[2];
	}
	void show_result() {
		cout << "\n --------------------------\n";
		cout << "\n Total Count : " << total;
		cout << "\n Total Price : " << totalPrice;
	}
};

int main() {
	Order p;
	p.accept_details();
	p.fix_order();
	p.calculate_price();
	p.show_result();
	return 0;
}

Enter the Number of Each Item to Order


BBQ Chickn : 1

Korean Spicy Korean : 2

Soybean Sauce Chicken : 3


Total Count : 6
Total Price : 60

Prac 2

class Derived;

class Base {
protected:
	int n1;
public:
	Base() :n1{ 10 } {}
	void show() {
		cout << "\n Value of Number 1 : " << n1;
	}
	friend void swap(Base*, Derived*);

};

class Derived {
protected:
	int n2;
public:
	Derived() :n2{ 20 } {}
	void show() {
		cout << "\n Value of Number 2: " << n2;
	}
	friend void swap(Base*, Derived*);

};

void swap(Base* b, Derived* d) {
	int tmp = b->n1;
	b->n1 = d->n2;
	d->n2 = tmp;
}

int main() {
	Base b;
	Derived d;
	b.show();
	d.show();

	swap(&b, &d);

	b.show();
	d.show();
	return 0;
}

Value of Number 1 : 10
Value of Number 2: 20
Value of Number 1 : 20
Value of Number 2: 10

Prac 3

class Engine {
private:
	int cylinder;
public:
	Engine(int n) {
		cylinder = n;
	}
	void start() {
		cout << getCylinder() << " cylinder engine started " << endl;
	}
	int getCylinder() {
		return cylinder;
	}
};

class Car :private Engine{
public:
	Car(int nc = 4) :Engine(nc) {}
	void start() {
		cout << "car with " << getCylinder() << " cylinder engine started\n";
		Engine::start();
	}
};

int main() {
	Car c(8);
	c.start();
	return 0;
}

car with 8 cylinder engine started
8 cylinder engine started

0개의 댓글