다형성은 편리하고 유용하지만
모든 경우에 사용할 수 없다.

출력연산자가 대표적인 경우이다.

출력연산자를 사용하려면 함수를 따로 하나 생성하여 오버라이딩 한다.
즉, 함수에게 기능을 위임한다.

비슷한 전략이 다른경우에도 사용될 수 있다.

 

핵심. 새로운 가상함수를 이용해 문제점을 해결한다.

#include <iostream>

using namespace std;

class Base
{
public:
	Base() {};

	friend ostream& operator << (ostream &out, const Base &b)
	{
		return b.print(out);
	}

	virtual ostream& print(ostream& out) const
	{
		out << "Base";
		return out;
	}

};

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

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

	Base b;
	cout << b << endl;

	Derived d;
	cout << d << endl;


	return 0;
}

자식 클래스의 포인터를 부모클래스의 포인터로 바꾼후 다시 자식클래스포인터로 변경하기.

auto와 dynamic_cast를 사용한다.

dynamic_cast는 casting에 실패하면 nullptr을 반환한다.

static_cast도 사용가능하다. static_cast는 할 수 있는한 최대한 변환시킨다.
안되야 될것도 되는 경우가 생긴다. 하지만 사용할 만한 가치는 있다.
static_cast는 nullptr을 반환하지 않는다.

 

#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_d = 0;
	virtual void print() override
	{
		cout << "I'm Derived1" << endl;
	}

};

class Derived2 : public Base
{
public:
	int m_d = 0;
	string m_s = "123";
	virtual void print() override
	{
		cout << "I'm Derived2" << endl;
	}

};
int main()
{

	Derived2 d2;
	Base *b = &d2;
	b->print();

	auto *base_to_d2 = dynamic_cast<Derived2*>(b);

	cout << base_to_d2->m_s << endl;


	return 0;
}

상속할때 보통 부모클래스보다는 자식클래스가 정보를 많이 가지고 있다.
이유 : 추가 함수,추가 변수,오버라이딩 등등

작은 부모의 객체에 큰 자식클래스를 대입한다면 다형성이 사라진다.

부모객체로 인식한다고 생각하면된다.

 

즉, 자식에 추가된 데이터들이 사라진다.


reference_wapper를 사용하거 포인터를 사용하면 객체잘림을 막을 수 있다.

 

당연, 포인터를 생각해보면 주소값을 대입하는거니 데이터는 그대로 남아있겠지?

 

#include <iostream>
#include <vector>

using namespace std;

class Base
{
public:
	int m_i = 0;
	virtual void print() 
	{
		cout << "I'm Base" << endl;
	}

};

class Der : public Base
{
public:
	int m_d = 0;
	virtual void print() override 
	{
		cout << "I'm Der" << endl;
	}

};
int main()
{
	Der d;
	Base b = d;
	b.print();
	
	//reference_wrapper를 사용하거나 *포인터 사용이가능하다.
	vector<reference_wrapper<Base>> my_vec;
	my_vec.push_back(b);
	my_vec.push_back(d);

	//reference_wrapper는 ref를 받아오기위해 get()을 사용한다.
	for (auto & a : my_vec)
		a.get().print();

	vector<Base*> my_vec2;
	my_vec2.push_back(&b);
	my_vec2.push_back(&d);

	for (auto & a : my_vec2)
		a->print();
	return 0;
}

다이아몬드 상속문제.

해결하기위해 가상 기본 클래스를 사용한다.
숫자는 class :는 상속을 나타낸다.
아래처럼 상속하면 다이아몬드 구조로 될거 같지만
1class가 두번 생성된다.
1
2 :(상속) 1 , 3 :(상속) 1
4 :(상속) 2,3

상속할때 상속하는 코드에 virtual을 쓰면 한번만 생성된다.
맨 마지막 자식 테이블에서 1class 생성자를 정의해준다.

 

#include <iostream>

using namespace std;
class PowerDevice
{

public:
	int m_power;
	PowerDevice(int power){
		cout << "PowerDevice : " << power << endl;
	}

};

class Scanner : virtual public PowerDevice
{
public:
	Scanner(int scan, int power)
		:PowerDevice(power) {
		cout << "Scanner : " << scan << endl;
	}
};

class Printer : virtual public PowerDevice
{
public:
	Printer(int prin, int power)
		:PowerDevice(power) {
		cout << "Printer : " << prin << endl;
	}
};

class Copier : public Scanner, public Printer
{
public:
	Copier(int scan, int prin, int power)
		:Scanner(scan, power), Printer(prin, power) ,PowerDevice(power){}
};
int main()
{
	Copier c(1, 2, 3);
	//주소값이 다르다.
	cout << &c.Scanner::PowerDevice::m_power << endl;
	cout << &c.Printer::PowerDevice::m_power << endl;
	return 0;
}

다형성을 설계관점을 만든다.

기본클래스에서 자식클래스에게 제약을 걸어준다.

순수 가상 함수
- 자식클래스에서 반드시 오버라이딩 해주어야한다.
- virtual 로 선언되고 마지막이 = 0; 이면 순수 가상 함수
- 별도로 함수를 구현할 수 있다. 호출할 수 없다.

virtual void speak() const = 0;



추상 기본클래스
- 순수 가상함수가 포함이된 클래스
- abstact class는 인스턴스생성이 안된다.

class Animal
{
protected:
	string m_name;

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

public:
	string getName() { return m_name; }

	virtual void speak() const = 0;
};


인터페이스
- 순수 가상 함수로만 이루어진 클래스
- 파라미터를 인터페이스로 받으면 다양하게 
구현된 자식 클래스들을 이용할 수 있다.

class IErrorlog
{
public:
	virtual bool reportError(const char * errorMessage) = 0;

	virtual ~IErrorlog() {}
};


순수 가상 함수가 있는 Class를 상속받으면 무조건 오버라이딩해야한다.

 

#include <iostream>
#include <string>

using namespace std;

class Animal
{
protected:
	string m_name;

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

public:
	string getName() { return m_name; }

	virtual void speak() const = 0;
};

//void Animal::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;
	}
};

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

	}

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

};


//Interface 예제
//자신이 정의하지 않고 순수가상함수만 가지고 있다.
//Interface는 대문자 I를 붙여주는게 관습이다.
class IErrorlog
{
public:
	virtual bool reportError(const char * errorMessage) = 0;

	virtual ~IErrorlog() {}
};

class FileErrorLog : public IErrorlog
{
public:
	virtual bool reportError(const char * errorMessage)
	{
		cout << "File Error" << endl;
		return true;
	}

};

//부모클래스를 이용하기에 여러방법이 사용가능하다.
void logTest(IErrorlog & log) {
	log.reportError("Runtime Error!!");
}

int main()
{
	//speak()가 구현이 안되어있다.
	//Cow c("n");

	Cat c("cat");
	c.speak();

	FileErrorLog log;
	logTest(log);

	return 0;
}

'개발 소발 > 개발 C++(기초)' 카테고리의 다른 글

c++ 객체잘림,reference wapper  (0) 2019.09.30
c++ 다이아몬드 상속문제 해결  (0) 2019.09.30
c++ vitual,가상함수  (0) 2019.09.30
c++ 정적,동적바인딩  (0) 2019.09.30
c++ 가상소멸자  (0) 2019.09.23

virtual 함수 (가상함수)는 정적이 아닌 동적으로 실행된다.

함수를 실행시 virtual펑션 테이블(포인터)을 찾고 테이블안에 함수 포인터를 사용한다.


다형성 사용시 자식 클래스에도 virtual펑션 테이블(포인터)가 생긴다.
동적바인딩 될때 오버라이딩된 함수는 자식 테이블 함수 주소 값을 가진다.

virtual을 사용하면 sizeof()로 확인시  포인터 메모리까지 할당되는 걸 볼 수 있다.

 

#include <iostream>

using namespace std;

class Base
{
public:
	virtual void fun1() {};
	void fun2() {};
};

class Der : Base
{
public:
	void fun1() {};
	void fun3() {};

};

int main()
{
	cout << sizeof(Base) << endl;
	cout << sizeof(Der) << endl;
	return 0;
}

가상함수의 내부동작 이해

속도면에선 static,dynamic중에 static이 빠르다.

정적 바인딩은 프로그램이 실행될때 메모리에 배치된다.
동적 바인딩은 그때 그때 배치된다.

 

dynamic바인딩이란? 

쉽게말해 함수 포인터로 동적할당해 그때그때 맞게 함수 주소를 대입한다.


dynamic은 포인터로 주소를 타고 가야한다.
하지만 dynamic 즉 동적바인딩을 쓰면 프로그래밍이 유연해진다.

 

#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;
	// statuc 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)
	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;
}

가상 소멸자

상속구조에서 생성자는 자식클래스는 자식클래스 생성자를 사용한다.
하지만 소멸자는 가상 소멸자를 사용하는게 좋다.

소멸자를 생성하지 않으면 자식소멸자,부모소멸자 순으로 실행된다.

하지만 부모클래스에 자식클래스를 대입한후 부모클래스를 delete하면 자식클래스의 소멸자가 실행안돼
메모리 릭이 생긴다.

부모클래스 소멸자에 virtual 을 해주면 자식 클래스의 소멸자도 실행시켜준다.

 

#include <iostream>

using namespace std;

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

};

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

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

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

};
int main()
{
	Derived *d = new Derived(5);
	Base *b = d;

	delete b;

	return 0;
}

'개발 소발 > 개발 C++(기초)' 카테고리의 다른 글

c++ vitual,가상함수  (0) 2019.09.30
c++ 정적,동적바인딩  (0) 2019.09.30
c++ override,final,공변 반환값  (0) 2019.09.23
c++ 가상함수와 다형성  (0) 2019.09.23
c++ 다형성의 기본개념  (0) 2019.09.23

override,final,공변 반환값

공변 반환형
Covariant

다형성쓸때 유용하게 쓸 수 있다.

오버로딩(파라미터가 다름)을 하면 상속이 적용안된다.

메소드명 뒤 override 사용하기!

오버라이드 할려했는데 실수로 오버로딩할 수 있다.

그걸 체크하려면 메소드 명 뒤에 override를 붙여준다.

class A {
virtual void printOverride(int x)  const{ cout << "A" << endl; }
}

class B : public A{
void printOverride(int x)  const override { cout << "B" << endl; }
}



메소드명 뒤 final 사용하기!

하위 클래스가 override하지 않게 하고싶을 수 있다.

상위 클래스 메소드명 뒤에 final을 붙여주면 오버라이드 할 수 없다.

virtual void printFinal(int x) final { cout << "A" << endl; }




반환값이 다르면 오버라이딩 안되지만

반환값이 부모자식관계면 오버라이딩이 된다.

오버라이딩은 되지만 부모 인스턴스에 자식을 대입하면 부모클래스가 출력된다.

class A{
	virtual A *getThis() { return this; }
};
class B : public A{
	B *getThis() { return this; }
};

 

코드 내용

#include <iostream>

using namespace std;

class A
{
public:
	virtual void printOverride(int x)  const{ cout << "A" << endl; }
	
	//final을 붙이면 오버라이드 할 수 없다.
	virtual void printFinal(int x) final { cout << "A" << endl; }
	
	void print() { cout << "A" << endl; }
	virtual A *getThis() { return this; }
};

class B : public A
{
public:
	void printOverride(int x)  const override { cout << "B" << endl; }
	
	//에러난다.
	//virtual void printFinal(int x) { cout << "B " << endl; }
	
	void print() { cout << "B" << endl; }
	B *getThis() { return this; }
};
int main() {

	A a;
	B b;
	
	A &ref = b;
	b.getThis()->print();
	ref.getThis()->print();

	cout << typeid(b.getThis()).name() << endl;
	//부모클래스가 출력된다.
	cout << typeid(ref.getThis()).name() << endl;
	return 0;
}

'개발 소발 > 개발 C++(기초)' 카테고리의 다른 글

c++ 정적,동적바인딩  (0) 2019.09.30
c++ 가상소멸자  (0) 2019.09.23
c++ 가상함수와 다형성  (0) 2019.09.23
c++ 다형성의 기본개념  (0) 2019.09.23
c++ 상속받은 함수를 오버라이딩하기  (0) 2019.08.30

가상함수와 다형성

다형성은 상속구조와 virtual을 사용한다.

virtual을 붙인 함수는 가상함수라고 부른다.

A,B,C,D 4가지 클래스를 생성하고 상속을 해보았다.

A의 포인터,Reference를 사용하며 A클래스의 메소드에 virtual을 사용하게 되면

자식클래스들은 모두 영향을 받는다.

즉, 가장 상위클래스의 virtual이 되면 밑에 클래스 모든 메소드도 virtual로 인식되게 된다.

상위클래스에만 있어도 되지만 형식적으로 자식클래스에도 virtual을 사용해놓는다.

오버라이딩한걸 인식하기 위해서 이다.

virtual 키워드는 스택처럼 차곡차곡 쌓는게 아닌 테이블을 찾는 과정이다.

찾아가는 과정이 있기에 속도가 느리다.

 

#include <iostream>
#include <string>

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

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

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

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

	//포인터도 사용가능
	//가장 상위클래스에 virtual이 선언되어 있으면 모두 영향 받는다.
	A &ref = c;
	ref.print();

	return 0; 
}

'개발 소발 > 개발 C++(기초)' 카테고리의 다른 글

c++ 가상소멸자  (0) 2019.09.23
c++ override,final,공변 반환값  (0) 2019.09.23
c++ 다형성의 기본개념  (0) 2019.09.23
c++ 상속받은 함수를 오버라이딩하기  (0) 2019.08.30
c++ 상속과 접근지정자  (0) 2019.08.30

+ Recent posts