본문 바로가기

개발 공부/C++

따라하며 배우는 C++ 11. 상속

11. 상속

 

11.1 상속의 기본(1) Inheritance (is-a relationship)

 

#include <iostream>
using namespace std;

class Mother
{
private:
	int m_i;
public:
	Mother(const int& i_in)
		: m_i(i_in)
	{
		cout << "Mother constructor" << endl;
	}
	Mother()
		: m_i(0)
	{
		cout << "Mother constructor" << endl;
	}
	int getValue()
	{
		return m_i;
	}
	void setValue(int i_in)
	{
		m_i = i_in;
	}
};

class Child : public Mother
{

};
int main() 
{ 
	Mother mother;
	mother.setValue(1024);
	cout << mother.getValue() << endl;

	Child child;
	child.setValue(123);
	cout << child.getValue() << endl;
	return 0;
}

Child가 Mother의 모든 멤버 및 기능을 다 재사용할 수 있다.

Chile class is derived class from Mother class.

Mother class is generalized class.

 

#include <iostream>
using namespace std;

class Mother
{
private:
	int m_i;
public:
	Mother(const int& i_in)
		: m_i(i_in)
	{
		cout << "Mother constructor" << endl;
	}
	Mother()
		: m_i(0)
	{
		cout << "Mother constructor" << endl;
	}
	int getValue()
	{
		return m_i;
	}
	void setValue(int i_in)
	{
		m_i = i_in;
	}
};

class Child : public Mother
{
private:
	double m_d;
public:
	void setValue(const double& d_in)
	{
		m_d = d_in;
	}
	double getValue()
	{
		return m_d;
	}
};
int main() 
{ 
	Mother mother;
	mother.setValue(1024);
	cout << mother.getValue() << endl;

	Child child;
	child.setValue(123.123);
	cout << child.getValue() << endl;
	return 0;
}#include <iostream>
using namespace std;

class Mother
{
private:
	int m_i;
public:
	Mother(const int& i_in)
		: m_i(i_in)
	{
		cout << "Mother constructor" << endl;
	}
	Mother()
		: m_i(0)
	{
		cout << "Mother constructor" << endl;
	}
	int getValue()
	{
		return m_i;
	}
	void setValue(int i_in)
	{
		m_i = i_in;
	}
};

class Child : public Mother
{
private:
	double m_d;
public:
	void setValue(const double& d_in)
	{
		m_d = d_in;
	}
	double getValue()
	{
		return m_d;
	}
};
int main() 
{ 
	Mother mother;
	mother.setValue(1024);
	cout << mother.getValue() << endl;

	Child child;
	child.setValue(123);
	cout << child.getValue() << endl;
	return 0;
}

메소드가 겹친다면 자식 class에 있는 것을 우선적으로 사용한다.

 

#include <iostream>
using namespace std;

class Mother
{
protected: //protected는 상속 관계에선 접근이 가능하도록 허용한다.
	//private일 경우 상속받은 클래스에서도 접근 불가하다.
	int m_i;
public:
	Mother(const int& i_in)
		: m_i(i_in)
	{
		cout << "Mother constructor" << endl;
	}
	Mother()
		: m_i(0)
	{
		cout << "Mother constructor" << endl;
	}
	int getValue()
	{
		return m_i;
	}
	void setValue(int i_in)
	{
		m_i = i_in;
	}
};

class Child : public Mother
{
private:
	double m_d;
public:
	Child(const int& i_in, const double& d_in)
		// :m_i(i_in), m_d(d_in) //불가능.
		// 생성자는 메모리를 초기에 할당받는 것이지,
		//이미 존재하는 메모리에 값을 복사하기 위함이 아님.
		//m_i(i_in)이 안 되는 이유는 자식 생성자가 호출될 때
		//m_i를 위한 메모리는 할당이 되지 않기 때문.
	{
		Mother::setValue(i_in);
		m_d = d_in;
	}
	void setValue(const int& i_in, const double& d_in)
	{
		//m_i = i_in; //Mother의 m_i 멤버가 private일 때는 접근 불가
		//멤버가 protected일 경우엔 가능.
		Mother::setValue(i_in); //Mother 클래스의 setValue를 확실히 지정해서 호출
		m_d = d_in;
	}
	void setValue(const double& d_in)
	{
		m_d = d_in;
	}
	double getValue()
	{
		return m_d;
	}
};
int main() 
{ 
	Mother mother;
	mother.setValue(1024);
	cout << mother.getValue() << endl;

	Child child(1024, 128);
	/* child.setValue(123.123); */
	//child.Mother::setValue(1); //도 가능.
	cout << child.Mother::getValue() << endl;
	cout << child.getValue() << endl;
	return 0;
}

Child 클래스는 생성되면서 Mother 클래스의 생성자를 같이 호출한다.

Child(const int& i_in, const double& d_in)
		: Mother(i_in), m_d(d_in)
	{
	}

디폴트 생성자를 만들어 놓는 것이 편하다. (다른 생성자를 만들면 디폴트 생성자가 사라지기 때문)

Child에서도 Mother의 생성자를 이용해 초기화하는 것이 편하다.

 

 

11.2 상속의 기본(2)

 

 

source.cpp

#include "Student.h"
#include "Teacher.h"
using namespace std;

int main() 
{ 
	Student std("Jack Jack");
	std.setName("Jack Jack 2");
	cout << std << endl;

	Teacher teacher1("Dr. H");
	cout << teacher1 << endl;

	std.doNothing();
	teacher1.doNothing();

	std.study();
	teacher1.teach();
	return 0;
}

 

Person.h

 

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

class Person
{

private:
	std::string m_name;
public:
	Person(const std::string& name_in = "No Name")
		: m_name(name_in)
	{}

	void setName(const std::string& name_in)
	{
		m_name = name_in;
	}

	std::string getName() const
	{
		return m_name;
	}

	void doNothing() const
	{
		cout << m_name<< " is doing nothing " << endl;
	}
};

 

 

Teacher.h

#pragma once
#include "Person.h"

class Teacher : public Person
{
private:
public:
	Teacher(const std::string& name_in = "No Name")
		: Person(name_in)
	{}

	void teach() const
	{
		cout << getName() << " is teaching " << endl;
	}

	friend std::ostream& operator << (std::ostream& out, const Teacher& teacher)
	{
		out << teacher.getName();
		return out;
	}
};

 

Student.h

 

#pragma once
#include "Person.h"

class Student : public Person
{
private:
	int m_intel; //intelligence;

public:
	Student(const std::string& name_in = "No Name", const int& intel_in = 0)
		//:m_name(name_in), m_intel(intel_in)
		:Person(name_in), m_intel(intel_in)
	{

	}

	void setIntel(const int& intel_in)
	{
		m_intel = intel_in;
	}

	int getIntel()
	{
		return m_intel;
	}

	void study() const
	{
		cout << getName() << " is studying " << endl;
	}


	friend std::ostream& operator << (std::ostream& out, const Student& student)
	{
		out << student.getName() << " " << student.m_intel;
		return out;
	}
};

 

11.3 유도된 클래스들의 생성 순서

 

#include <iostream>
using namespace std;


class Mother
{
public:
	int m_i;
};

class Child : public Mother
{
public:
	Child()
		// : m_i(1024) //생성자로 초기화가 안 됨.
	{
		//this->m_i = 10;
		//this-Mother::m_i = 1024;
	}
};
int main() 
{ 
	return 0;
}

 

#include <iostream>
using namespace std;


class Mother
{
public:
	int m_i;
public:
	Mother()
		: m_i(1)
	{
		cout << "Mother construction " << endl;
	}
};

class Child : public Mother
{
public:
	double m_d;
public:
	Child()
		: m_d(1.0)
	{
		cout << "Child construction" << endl;
	}
};
int main() 
{ 
	Child c1;
	return 0;
}

Mother costructor가 호출되고 그 다음 Child가 호출된다.

 

Child()

   : Mother(), m_d(1.0)

처럼 Mother의 생성자가 Child 생성자에 숨어 있는 것.

 

#include <iostream>
using namespace std;


class Mother
{
public:
	int m_i;
public:
	Mother(const int & i_in = 0) //기본값 넣어주면 default constructor
		//문제까지 한 방에 해결됨
		: m_i(i_in)
	{
		cout << "Mother construction " << endl;
	}
};

class Child : public Mother
{
public:
	double m_d;
public:
	Child()
		: Mother(1024), m_d(1.0)
	{
		cout << "Child construction" << endl;
	}
};
int main() 
{ 
	Child c1;
	return 0;
}
#include <iostream>
using namespace std;

class A
{
public:
	A()
	{
		cout << "A constructor" << endl;
	}
};

class B : public A
{
public:
	B()
	{
		cout << "B constructor" << endl;
	}
};

class C : public B
{
public:
	C()
	{
		cout << "C constructor" << endl;
	}
};

int main() 
{ 
	C c;
	return 0;
}

이렇게 상속을 여러 번 받을 수 있다.

C 생성에서 A->B->C 생성자 순으로 호출됨.

 

 

11.4 유도된 클래스들의 생성과 초기화

 

#include <iostream>
using namespace std;

class Mother
{
public:
	int m_i;
public:
	Mother(const int& i_in = 0)
		: m_i(i_in)
	{
		cout << "Mother construction " << endl;
	}
};

class Child : public Mother
{
private:
	float m_d;
public:
	Child()
		: m_d(1.0f), Mother(1024)
	{
		cout << "Child construction " << endl;
	}
};

int main() 
{ 
	Child c1;
	cout << sizeof(Mother) << endl;
	cout << sizeof(Child) << endl;
	return 0;
}

Child에서 메모리를 할당할 때는 Mother 클래스의 것도 다 담을 수 있을만큼 크게 할당받는다.

#include <iostream>
using namespace std;

class A
{
public:
	A(int a)
	{
		cout << "A: " << a << endl;
	}
	~A()
	{
		cout << "Destuctor A" << endl;
	}
};

class B : public A
{
public:
	B(int a, double b)
		: A(a)
	{
		cout << "B: " << b << endl;
	}
	~B()
	{
		cout << "Destuctor B" << endl;
	}
};

class C : public B
{
public:
	C(int a, double b, char c)
		: B(a, b)
	{
		cout << "C: " << c << endl;
	}
	~C()
	{
		cout << "Destuctor C" << endl;
	}
};

int main() 
{ 
	C c(1024, 3.14, 'a');
	return 0;
}

소멸자 호출 순서

 

 

11.5 상속과 접근 지정자

 

#include <iostream>
using namespace std;

class Base
{
public:
	int m_public;
protected:
	int m_protected;
private:
	int m_private;
};

class Derived : public Base
{
public:
	Derived()
	{
		m_public = 123;
		m_protected = 1234;
	}
};
int main() 
{ 
	Base base;
	base.m_public = 123;

	return 0;
}
#include <iostream>
using namespace std;

class Base
{
public:
	int m_public;
protected:
	int m_protected;
private:
	int m_private;
};

class Derived : protected Base
{
public:
	Derived()
	{
		Base::m_public;
		Base::m_protected;
	}
};
int main() 
{
	Derived d;
	// d.m_public; //불가
	return 0;
}
#include <iostream>
using namespace std;

class Base
{
public:
	int m_public;
protected:
	int m_protected;
private:
	int m_private;
};

class Derived : private Base
{
public:
	Derived()
	{
		Base::m_public;
		Base::m_protected;
	}
};

class GrandChild : public Derived
{
public:
	GrandChild()
	{
		/*Derived::m_public;
		Derived::m_protected;*/ //불가
	}
};
int main() 
{
	Derived d;
	// d.m_public; //불가
	return 0;
}

 

 

상속 시 접근 지정자를 설정하면, 그 지정자보다 더 안전하지 않은 멤버가 그 접근 지정자처럼 지정된다.

ex. private 부모 클래스로 상속 받으면, public한 멤버라도 private처럼 작동한다.

 

 

11.6 유도된 클래스에 새로운 기능 추가하기

 

#include <iostream>
using namespace std;
class Base
{
protected:
	int m_value;
public:
	Base(int value)
		: m_value(value)
	{

	}
};

class Derived : public Base
{
public:
	Derived(int value)
		: Base(value)
	{

	}

	void setValue(int value)
	{
		Base::m_value = value;
		//do some work with the variables defined in Derived.
		//protected로 하면 자식 클래스에서
		//부모 클래스의 멤버에 접근 가능.
	}
};
int main() 
{
	// d.m_public; //불가
	return 0;
}

 

11.7 상속받은 함수Inherited Functions를 오버라이딩Overriding하기.

 

#include <iostream>
using namespace std;
class Base
{
protected:
	int m_value;
public:
	Base(int value)
		: m_value(value)
	{

	}
	void print()
	{
		cout << "I'm base" << endl;
	}
};

class Derived : public Base
{
public:
	Derived(int value)
		: Base(value)
	{

	}
	void print()
	{
		cout << "I'm derived" << endl;
	}
};
int main() 
{
	Base base(5);
	base.print();

	Derived derived(7);
	derived.print();
	return 0;
}

 

 

#include <iostream>
using namespace std;
class Base
{
protected:
	int m_value;
public:
	Base(int value)
		: m_value(value)
	{

	}
	void print()
	{
		cout << "I'm base" << endl;
	}
};

class Derived : public Base
{
public:
	Derived(int value)
		: Base(value)
	{

	}
	void print()
	{
		Base::print();
		cout << "I'm derived" << endl;
	}
};
int main() 
{
	Base base(5);
	base.print();

	Derived derived(7);
	derived.print();
	return 0;
}

 

#include <iostream>
using namespace std;
class Base
{
protected:
	int m_value;
public:
	Base(int value)
		: m_value(value)
	{

	}
	void print()
	{
		cout << "I'm base" << endl;
	}
	friend std::ostream& operator << (std::ostream& out, const Base& b)
	{
		out << "This is base output" << endl;
		return out;
	}
};

class Derived : public Base
{
public:
	Derived(int value)
		: Base(value)
	{

	}
	void print()
	{
		Base::print();
		cout << "I'm derived" << endl;
	}
	friend std::ostream& operator << (std::ostream& out, const Derived &d)
	{
		out << "This is derived output" << endl;
		return out;
	}
};
int main() 
{
	Base base(5);
	cout << base;

	Derived derived(7);
	cout << derived;
	return 0;
}

#include <iostream>
using namespace std;
class Base
{
protected:
	int m_value;
public:
	Base(int value)
		: m_value(value)
	{

	}
	void print()
	{
		cout << "I'm base" << endl;
	}
	friend std::ostream& operator << (std::ostream& out, const Base& b)
	{
		out << "This is base output" << endl;
		return out;
	}
};

class Derived : public Base
{
public:
	Derived(int value)
		: Base(value)
	{

	}
	void print()
	{
		Base::print();
		cout << "I'm derived" << endl;
	}
	friend std::ostream& operator << (std::ostream& out, const Derived &d)
	{
		cout << static_cast<Base>(d);
		out << "This is derived output" << endl;
		return out;
	}
};
int main() 
{
	Base base(5);
	cout << base;

	Derived derived(7);
	cout << derived;
	return 0;
}

Derived로 Base를 캐스팅하면, Base의 함수로 나온다.

 

 

11.8 상속 받은 함수를 감추기

 

#include <iostream>
using namespace std;
class Base
{
protected:
	int m_i;
public:
	Base(int value)
		: m_i(value)
	{

	}
	void print()
	{
		cout << "I'm base" << endl;
	}
};

class Derived : public Base
{
public:
	Derived(int value)
		: Base(value)
	{

	}
	
	using Base::m_i;
};
int main() 
{
	Derived derived(7);
	derived.m_i = 1024;
	return 0;
}

 

유도된 클래스에서 부모 클래스의 멤버를 public으로 바꿀 수 있다.

 

#include <iostream>
using namespace std;
class Base
{
protected:
	int m_i;
public:
	Base(int value)
		: m_i(value)
	{

	}
	void print()
	{
		cout << "I'm base" << endl;
	}
};

class Derived : public Base
{
public:
	Derived(int value)
		: Base(value)
	{

	}
	
	using Base::m_i;
private:
	using Base::print; //do not add ()
};
int main() 
{
	Derived derived(7);
	derived.m_i = 1024;
	//derived.print() //불가
	return 0;
}

함수도 접근 지정자를 바꿀 수 있다.

혹은 void print() = delete; 를 하면 부모의 기능을 삭제할 수 있다.

 

 

11.9 다중 상속 Multiple inheritance

 

#include <iostream>
using namespace std;
class USBDevice
{
private:
	long m_id;
public:
	USBDevice(long id) : m_id(id) {}
	long getID() { return m_id; }
	void plugAndPlay() {}
};

class NetworkDevice
{
private:
	long m_id;
public:
	NetworkDevice(long id) : m_id(id) {}
	long getID() { return m_id; }
	void networking() {}
};

class USBNetworkDevice : public USBDevice, public NetworkDevice
{
public:
	USBNetworkDevice(long usb_id, long net_id)
		: USBDevice(usb_id), NetworkDevice(net_id)
	{}
};
int main() 
{
	USBNetworkDevice my_device(3.14, 6.022);

	my_device.networking();
	my_device.plugAndPlay();

	my_device.USBDevice::getID();
	my_device.NetworkDevice::getID();
	return 0;
}

위처럼 여러 개를 상속받을 수 있지만, 메소드가 겹치는 경우 어떤 부모 클래스에 지정되어 있던 메소드인지

명기해줄 필요가 있다.

 

 

다이아몬드 상속이 문제가 될 수 있다.