반응형
다형성의 기본 개념
자식 클래스 객체에 부모 클래스 포인터를 사용한다면?
#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++' 강의를 수강하며 개인 백업용으로 메모하였습니다.
반응형