profile image

L o a d i n g . . .

반응형

다형성의 기본 개념

자식 클래스 객체에 부모 클래스 포인터를 사용한다면?

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

class Animal
{
protected:
	string m_name;

public:
	Animal(std::string name)
		: m_name(name)
	{}

public:
	string getName() { return m_name; }

	//void speak() const
	virtual void speak() const
	{
		cout << m_name << " ??? " << endl;
	}
};

class Cat : public Animal
{
public:
	Cat(string name)
		: Animal(name)
	{}

	void speak() const
	{
		cout << m_name << " Meow " << endl;
	}
};

class Dog : public Animal
{
public:
	Dog(string name)
		: Animal(name)
	{}

	void speak() const
	{
		cout << m_name << " Woof " << endl;
	}
};

int main()
{
	Animal animal("my animal");
	Cat cat("my cat");
	Dog dog("my dog");

	animal.speak();
	cat.speak();
	dog.speak();

	// 자식 클래스를 부모 클래스의 포인터로 캐스팅하면..
	Animal *ptr_animal1 = &cat;
	Animal *ptr_animal2 = &dog;

	// 각각의 울음소리가 아닌 ???(Animal::speak) 출력
	ptr_animal1->speak();
	ptr_animal2->speak();

	cout << endl;


	Cat cats[] = { Cat("cat1"), Cat("cat2") , Cat("cat3") , Cat("cat4") , Cat("cat5") };
	Dog dogs[] = { Dog("dog1"), Dog("dog2") };

	for (int i = 0; i < 5; i++)
		cats[i].speak();

	for (int i = 0; i < 2; i++)
		dogs[i].speak();
	// .....

	cout << endl;

	Animal *my_animals[] = { &cats[0], &cats[1], &cats[2], &cats[3], &cats[4],
							 &dogs[0], &dogs[1]};

	// 한 개의 for문으로 처리 가능!
	// virtual이 없으면 그냥 Animal의 speak() 실행, virtual 붙이면 각각의 울음소리 출력
	for (int i = 0; i < 7; i++)
		my_animals[i]->speak();

	return 0;
}

Animal 배열이 아니라 컨테이너나 벡터로 사용하면 조금 더 편하게 사용할 수 있음!

 

 

가상 함수와 다형성

virtual은 virtual 테이블에서 찾아감 => 느림! 속도가 빨라야 한다면 사용하지 않는 편이 좋다.

#include <iostream>
using namespace std;

class A
{
public:
	virtual void print() { cout << "A" << endl; }
};

class B : public A
{
public:
	//void print() { cout << "B" << endl; }
	virtual void print() { cout << "B" << endl; }
};

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

class D : public C
{
public:
	//virtual int print() { cout << "D" << endl; }	// error!
	void print() { cout << "D" << endl; }
};

int main()
{
	A a;
	B b;
	C c;
	D d;

	A &ref = b;
	ref.print();	// virtual X: A / virtual O: B 출력

	// B가 C를 상속받지 않는다면 B가 출력
	// 가장 상위 클래스에 virtual 선언을 한다면 하위 클래스 전부가 그렇게 작동함!!
	// 상속받는 것들도 virtual 키워드를 붙이는 게 관습임 => 디버깅할 때 편리함
	B &ref2 = c;
	ref2.print();

	return 0;
}

 

 

override, final, 공변 반환값

#include <iostream>
using namespace std;

class A
{
public:
   virtual void print() { cout << "A" << endl; }
   //virtual A* getThis() { return this; }
};

class B : public A
{
public:
   // 매개변수가 다르면/const 유무가 다르면 overriding 불가능
   // => override 선언하면 컴파일러가 에러 찾아 줌
   //void print(short x) { cout << "B" << endl; }
   void print() final { cout << "B" << endl; }
   //void print1() { cout << "B" << endl; }
   //virtual B* getThis() { return this; }
};

class C : public B
{
public:
   // 부모 클래스에서 final 선언 시 override 불가능
   //virtual void print() { cout << "C" << endl; }
};

int main()
{
   A a;
   B b;

   A &ref = b;
   //ref.print(1);   // 출력: A
   //cout << typeid(b.getThis()).name() << endl;

   return 0;
}

 

#include <iostream>
using namespace std;

//covariant
class A
{
public:
   void print() { cout << "A" << endl; }
   virtual A* getThis() 
   {
      cout << "A::getThis()" << endl;
      return this; 
   }
};

class B : public A
{
public:
   void print() { cout << "B" << endl; }
   virtual B* getThis() 
   {
      cout << "B::getThis()" << endl;
      return this; 
   }
};

class C : public B
{
public:
   virtual void print() { cout << "C" << endl; }
};

int main()
{
   A a;
   B b;
   //C c;

   A &ref = b;
   b.getThis()->print();   // 출력: B
   ref.getThis()->print();   // 출력: A

   cout << typeid(b.getThis()).name() << endl;      // 출력: class B *
   cout << typeid(ref.getThis()).name() << endl;   // 출력: class A *

   return 0;
}

 

 

가상 소멸자

#include <iostream>
using namespace std;

class Base
{
public:
	~Base()
	{
		cout << "~Base()" << endl;
	}
};

class Derived : public Base
{
private:
	int *m_array;

public:
	Derived(int length)
	{
		m_array = new int[length];
	}

	~Derived()
	{
		cout << "~Derived()" << endl;
		delete[] m_array;
	}
};

int main()
{
	// 상속 구조에 따라서 자식 소멸자 후 부모 소멸자 호출
	Derived derived(5);
	cout << endl;

	// Base를 지우는 것이 일반적임 => Base()의 소멸자만 호출됨
	// 동적 할당 시 메모리 누수 발생!
	Derived *derived2 = new Derived(5);
	Base *base = derived2;
	delete base;

	return 0;
}

#include <iostream>
using namespace std;

class Base
{
public:
	// 부모 클래스의 소멸자를 virtual로 선언하면 해결!
	virtual ~Base()
	{
		cout << "~Base()" << endl;
	}
};

class Derived : public Base
{
private:
	int *m_array;

public:
	Derived(int length)
	{
		m_array = new int[length];
	}

	// 부모 소멸자에 virtual 붙였으면 자식 소멸자에 override 표시할 수 있음
	~Derived() override
	{
		cout << "~Derived()" << endl;
		delete[] m_array;
	}
};

int main()
{
	Derived *derived2 = new Derived(5);
	Base *base = derived2;
	delete base;

	return 0;
}

 

 

동적 바인딩과 정적 바인딩

#include <iostream>
using namespace std;

int add(int x, int y)
{
	return x + y;
}

int subtract(int x, int y)
{
	return x - y;
}

int multiply(int x, int y)
{
	return x * y;
}

int main()
{
	int x, y;
	cin >> x >> y;

	int op;
	cout << "0 : add, 1 : subtract, 2 : multiply" << endl;
	cin >> op;

	// static binding (early binding)
	// 모든 변수명/함수명이 빌드할 때 정해져 있음
	// 속도가 빠름
	int result;
	switch (op)
	{
	case 0: result = add(x, y); break;
	case 1: result = subtract(x, y); break;
	case 2: result = multiply(x, y); break;
	}
	cout << result << endl;

	
	// dynamic binding (late binding)
	// function pointer에 어떤 함수 주소가 들어갈지 "런타임"에 결정됨
	// 속도가 static binding보다 느리나, 프로그래밍이 유연해짐!
	int(*func_ptr)(int, int) = nullptr;
	switch (op)
	{
	case 0: func_ptr = add; break;
	case 1: func_ptr = subtract; break;
	case 2: func_ptr = multiply; break;
	}
	cout << func_ptr(x, y) << endl;

	return 0;
}

 

 

가상 (함수) 표

virtual 키워드가 있다면, virtual function 포인터를 지니게 된다.

자식 클래스로 생성된 객체를 부모 클래스에 집어 넣어도 테이블이 바뀌지 않기 때문에 구조를 유지하며 다형성을 사용할 수 있다.

#include <iostream>
using namespace std;

class Base
{
public:
	//FuctionPointer *__vptr;
	virtual void fun1() {};
	virtual void fun2() {};
};

class Der : public Base
{
public:
	//FuctionPointer *__vptr;
	virtual void fun1() {};
	virtual void fun2() {};
};

int main()
{
	cout << sizeof(Base) << endl;	// 32비트 기준 출력: 1(virtual X) / 4(virtual O)
	cout << sizeof(Der) << endl;

	return 0;
}

 

 

순수 가상 함수, 추상 기본 클래스, 인터페이스 클래스

설계하는 관점에서 이러한 것들을 지켜야 한다고 기본 클래스에 명시해 둔다.

순수 가상 함수(pure virtual function): body가 없기 때문에 무조건 자식 클래스에서 override해서 사용해야 함!

추상 클래스, 기본 클래스(Abstract class): 순수 가상 함수가 포함된 클래스

인터페이스 클래스(Interface class): 순수 가상 함수로만 이루어진 클래스

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

class Animal
{
protected:
	string m_name;

public:
	Animal(std::string name)
		: m_name(name)
	{}

public:
	string getName() { return m_name; }

	// body가 없는 virtual function인데 = 0 되어 있는 함수 => pure virtual function
	virtual void speak() const = 0;
	/*
	virtual void speak() const
	{
		cout << m_name << " ??? " << endl;
	}*/
};

/*
void Animal::speak() const	// the body of the pure virtual function
{
	cout << m_name << " ??? " << endl;
}*/

class Cat : public Animal
{
public:
	Cat(string name)
		: Animal(name)
	{}

	void speak() const
	{
		cout << m_name << " Meow" << endl;
	}
};

class Dog : public Animal
{
public:
	Dog(string name)
		: Animal(name)
	{}

	void speak() const
	{
		cout << m_name << " Woof" << endl;
	}
};

class Cow : public Animal
{
public:
	Cow(string name)
		: Animal(name)
	{}

	void speak() const
	{
		cout << m_name << " Moooo" << endl;
	}
};

int main()
{
	//Animal ani("Hi");	// error! abstract class라서 불가능!

	Cow cow("hello");	// Animal::speak() 재정의 필요!!
	cow.speak();


	return 0;
}

 

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

// interface class는 실제로 뭘 할지는 없고, 무엇을 해야 한다고 가이드만 제공함
// interface class의 경우 앞에 I 붙이는 것이 관습임
class IErrorLog
{
public:
	virtual bool reportError(const char * errorMessage) = 0;

	virtual ~IErrorLog() {}
};

class FileErrorLog : public IErrorLog
{
public:
	bool reportError(const char * errorMessage) override
	{
		cout << "Writing error to a file" << endl;
		return true;
	}
};

class ConsoleErrorLog : public IErrorLog
{
public:
	bool reportError(const char * errorMessage) override
	{
		cout << "Writing error to a console" << endl;
		return true;
	}
};

// interface를 매개변수로 받을 수 있음!
void doSomething(IErrorLog & log)
{
	log.reportError("Runtime error!!");
}

int main()
{
	FileErrorLog file_log;
	ConsoleErrorLog console_log;

	doSomething(file_log);
	doSomething(console_log);

	return 0;
}

 

 

가상 기본 클래스와 다이아몬드 상속 문제

#include <iostream>
using namespace std;

class PoweredDevice
{
public:
	int m_i;

	PoweredDevice(int power)
	{
		cout << "PoweredDevice: " << power << '\n';
	}
};

class Scanner : public PoweredDevice
{
public:
	Scanner(int scanner, int power)
		: PoweredDevice(power)
	{
		cout << "Scanner: " << scanner << '\n';
	}
};

class Printer : public PoweredDevice
{
public:
	Printer(int printer, int power)
		: PoweredDevice(power)
	{
		cout << "Printer: " << printer << '\n';
	}
};

class Copier : public Scanner, public Printer
{
public:
	Copier(int scanner, int printer, int power)
		: Scanner(scanner, power), Printer(printer, power)
	{
	}
};


int main()
{
	Copier cop(1, 2, 3);

	// PoweredDevice 생성자 두 번 호출 및 주소가 다름!! 서로 다른 두 개의 PoweredDevice 생성
	cout << &cop.Scanner::PoweredDevice::m_i << endl;
	cout << &cop.Printer::PoweredDevice::m_i << endl;

	return 0;
}

#include <iostream>
using namespace std;

class PoweredDevice
{
public:
	int m_i;

	PoweredDevice(int power)
	{
		cout << "PoweredDevice: " << power << '\n';
	}
};

//class Scanner : public PoweredDevice
class Scanner : virtual public PoweredDevice
{
public:
	Scanner(int scanner, int power)
		: PoweredDevice(power)
	{
		cout << "Scanner: " << scanner << '\n';
	}
};

//class Printer : public PoweredDevice
class Printer : virtual public PoweredDevice
{
public:
	Printer(int printer, int power)
		: PoweredDevice(power)
	{
		cout << "Printer: " << printer << '\n';
	}
};

class Copier : public Scanner, public Printer
{
public:
	Copier(int scanner, int printer, int power)
		: Scanner(scanner, power), Printer(printer, power),
		PoweredDevice(power)	// 별도 호출 필요함
	{
	}
};


int main()
{
	Copier cop(1, 2, 3);

	cout << &cop.Scanner::PoweredDevice::m_i << endl;
	cout << &cop.Printer::PoweredDevice::m_i << endl;

	return 0;
}

 

 

객체 잘림과 reference wrapper

보다 큰 자식 클래스의 객체를 부모 클래스에 담는다면? 담을 수 있는 정보가 적기 때문에 자식 클래스에만 있는 것들은 사라져 버린다.

#include <iostream>
#include <vector>
#include <functional>

using namespace std;

class Base
{
public:
	int m_i = 0;

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

class Derived : public Base
{
public:
	int m_j = 1;

	virtual void print() override
	{
		cout << "I'm derived" << endl;
	}
};

void doSomething(Base & b)
{
	b.print();
}

void doSomething2(Base b)
{
	b.print();
}

int main()
{
	Derived d;
	Base &b = d;
	//Base b = d;	// 복사 대입, d만 가지고 있는 정보는 b가 가질 수 없음!! => 의도적인 것이라면 주석을 달아 주는 것이 좋음

	b.print();

	doSomething(d);
	doSomething2(d);	// 매개변수 자리에 &를 빼 버리면 다형성이 사라짐!

	return 0;
}

#include <iostream>
#include <vector>
#include <functional>

using namespace std;

class Base
{
public:
	int m_i = 0;

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

class Derived : public Base
{
public:
	int m_j = 1;

	virtual void print() override
	{
		cout << "I'm derived" << endl;
	}
};

int main()
{
	Base b;
	Derived d;

	// 1. Base 타입 vector
	std::vector<Base> my_vec;

	my_vec.push_back(b);
	my_vec.push_back(d);

	for (auto & ele : my_vec)
		ele.print();
	cout << endl;

	// 2. Base *타입 vector
	std::vector<Base *> my_vec2;
	my_vec2.push_back(&b);
	my_vec2.push_back(&d);

	for (auto &ele : my_vec2)
		ele->print();
	cout << endl;


	// 3. Base &타입 vector
	//std::vector<Base&> my_vec;	//error! 'data': pointer to reference is illegal

	// #include <functional> 필요!
	std::vector<std::reference_wrapper<Base>> my_vec3;

	my_vec3.push_back(b);
	my_vec3.push_back(d);

	for (auto & ele : my_vec3)
		ele.get().print();

	return 0;
}

 

 

동적 형변환

Dynamic Casting

#include <iostream>
#include <string>

using namespace std;

class Base
{
public:
	int m_i = 0;

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

class Derived1 : public Base
{
public:
	int m_j = 1024;

	virtual void print() override
	{
		cout << "I'm derived" << endl;
	}
};

class Derived2 : public Base
{
public:
	string m_name = "Dr. Two";

	virtual void print() override
	{
		cout << "I'm derived" << endl;
	}
};

int main()
{
	Derived1 d1;
	Base *base = &d1;	// Base에는 없고 Derived에만 있는 것들에는 접근 불가능
	d1.m_j = 2048;

	// 가급적 이런 식으로 코딩은 지양하기...
	auto *base_to_d1 = dynamic_cast<Derived1*>(base);
	cout << base_to_d1->m_j << endl;

	base_to_d1->m_j = 256;
	cout << d1.m_j << endl;

	// dynamic_cast는 casting에 실패하면 nullptr를 넣어 버림
	// 같은 부모를 가지고 있어도 이런 식으로 d1=>d2 형변환은 불가능함!
	auto *base_to_d2 = dynamic_cast<Derived2*>(base);
	if (base_to_d2 != nullptr)
		base_to_d2->print();
	else
		cout << "Failed" << endl;

	// static_cast도 가능함
	auto *base_to_d1_2 = static_cast<Derived1*>(base);
	if (base_to_d1_2 != nullptr)
		base_to_d1_2->print();
	else
		cout << "Failed" << endl;

	// 단, static cast는 최대한 형변환하려고 하기 때문에 원래라면 안 되는 것들도 되는 경우가 있음
	auto *base_to_d2_2 = static_cast<Derived2*>(base);
	if (base_to_d2_2 != nullptr)
		base_to_d2_2->print();
	else
		cout << "Failed" << endl;

	// 문제가 안 생기는 경우에는 뭘 쓰든 상관이 없지만,
	// dynamic_cast는 런타임에 체크를 해서 아닌 것 같은 경우 nullptr를 넣어 주나
	// static_cast는 그런 기능이 없고 무조건 형변환함

	return 0;
}

 

 

유도 클래스에서 출력 연산자 사용하기

출력 연산자는 overriding할 수 없음!

#include <iostream>

class Base
{
public:
	Base() {}

	// friend는 멤버가 아니기 때문에 직접 overriding을 할 수 없음!!
	friend std::ostream& operator << (std::ostream &out, const Base &b)
	{
		return b.print(out);
	}

	// 직접적인 일은 이쪽으로 넘기게끔 해서 overriding처럼 구현 가능
	virtual std::ostream& print(std::ostream& out) const
	{
		out << "Base";
		return out;
	}
};

class Derived : public Base
{
public:
	Derived() {}

	virtual std::ostream& print(std::ostream& out) const override
	{
		out << "Derived";
		return out;
	}
};

using namespace std;

int main()
{
	Base b;
	std::cout << b << '\n';

	Derived d;
	std::cout << d << '\n';

	Base &bref = d;
	std::cout << bref << '\n';

	// 어떠한 기능을 virtual 함수 여러 개를 조합하는 방식으로 구현하는 방식도 있음
	// => 일반적인 프로그래밍 전략으로도 많이 사용됨

	return 0;
}

 

 

해당 포스트는 '홍정모의 따라하며 배우는 C++' 강의를 수강하며 개인 백업용으로 메모하였습니다.

인프런: https://www.inflearn.com/course/following-c-plus

 

홍정모의 따라하며 배우는 C++ - 인프런

만약 C++를 쉽게 배울 수 있다면 배우지 않을 이유가 있을까요? 성공한 프로그래머로써의 경력을 꿈꾸지만 지금은 당장 하루하루 마음이 초조할 뿐인 입문자 분들을 돕기 위해 친절하고 자세하게 설명해드리는 강의입니다. 초보로 시작하더라도 중급을 넘어 고급 프로그래머로 가는 길목에 들어서고 싶으시다면 최고의 디딤돌이 되어드리겠습니다. 여러분의 꿈을 응원합니다! 초급 프로그래밍 언어 C++ 온라인 강의 C++

www.inflearn.com

반응형
복사했습니다!