2023. 1. 29. 10:04ㆍProgramming/JAVA, C++, Go, Rust
- 목차
class Animal {
public:
Animal& operator=(const Animal& rhs);
};
class Bird: public Animal {
public:
Bird& operator=(const Bird& rhs);
};
이제 Bird를 잘 못 사용하는 예를 살펴보겠습니다.
Bird b1;
Bird b2;
Animal *pa1 = &b1;
Animal *pa2 = &b2;
*pa1 = *pa2;
위와 같이 치환하게 되면 Base class인 Animal의 치환연산자는 virtual로 선언되지 (vtbl을 생성 하지) 않았으므로, Bird의 치환을 예상했지만, Bird가 가지고 있는 Animal의 치환연산자만 호출됩니다. 즉, Animal의 내용만 치환되게 됩니다. 이렇게 포인터를 직접적으로 사용하는 경우들 (오래된 C 프로그래머들이 C++을 사용)이 종종 발생합니다.
그래서, 이렇게 상속 구조를 갖게 될 경우 base class의 연산자에도 virtual을 사용합니다.
class Animal {
public:
virtual Animal& operator=(const Animal& rhs);
};
class Bird: public Animal {
public:
virtual Bird& operator=(const Animal& rhs);
};
class Dog: public Animal {
public:
virtual Dog& operator=(const Animal& rhs);
};
그런데, 위와 같이 처리하게되면 다음과 같이 서로 다른 객체의 치환이 가능합니다.
Bird b1;
Dog d1;
Animal *pa1 = &b1;
Animal *pa2 = &d1;
*pa1 = *pa2;
타입 불일치 상황이 발생합니다.
즉, 구현에서 runtime에 type check 가 가능하도록 dynamic_cast를 사용합니다.
Dog& Dog::opeerator=(const Animal& rhs) {
const DoG& rhsDog = dynamic_cast<const Dog &>(rhs);
...
}
type이 다를 경우 bad_cast 예외가 발생합니다.
사실 pointer만 사용하지 않으면 그냥 객체를 대입하면 됩니다.
Dog d1;
Dog d2;
d1 = d2;
그저 자신의 type을 받는 치환연산자만 정의하면 됩니다.
class Dog: public Animal {
public:
Dog& operator=(const Dog& rhs);
};
이렇게 pointer를 사용한 치환과 정상적인 치환 두가지를 모두 대응하도록 class를 설계합니다.
Dog class는 다음의 두가지를 지닙니다.
virtual Dog& operator=(const Animal& rhs);
Dog& operator=(const Dog& rhs);
애당초 이런 대입을 막으면 되지 않을까요?
그러려면, Animal의 operator=를 private로 선언합니다.
Bird b1;
Dog d1;
Animal *pa1 = &b1;
Animal *pa2 = &d1;
*pa1 = *pa2;
이제 위의 치환 코드는 에러가 발생합니다.
그런데 Animal끼리의 치환도 에러가 발생합니다.
그래서 Animal 객체는 대입이 불필요한 추상 클래스로 선언합니다.
'Programming > JAVA, C++, Go, Rust' 카테고리의 다른 글
C++ cast들 (static_cast, reinterpret_cast, dynamic_cast, const_cast) (0) | 2023.02.09 |
---|---|
C++ emplace_back (0) | 2023.02.03 |
미래지향적인 C++ 프로그래밍 (0) | 2023.01.29 |
C++ 헤더 내 구현 장/단점 (0) | 2023.01.27 |
C++ string split (0) | 2023.01.27 |