[ Effective C++ ] 항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자

Minsu._.Lighting·2023년 12월 5일
0

[ Effective C++ ] 정리 모음집
" C++ 프로그래머의 필독서, 스콧 마이어스의 Effective C++ 를 읽고 내용 요약 / 정리 "

[핵심]

" 캐스트는 온갖 골칫거리를 다 몰고 다니는 풍운아! "

  • 다른 방법이 가능하다면 캐스팅은 피하자! 특히 수행 성능에 민감한 코드에서 dynamic_cast는 몇번이고 다시 생각해보고, 설계 중 캐스팅이 필요해 졌다면 캐스팅을 쓰지않는 방법을 시도해 보자!
  • 캐스팅이 어쩔 수 없이 필요하다면, 사용자가 자신의 코드에 캐스팅을 넣지 않고 함수를 호출할 수 있게 함수 안에 숨길 수 있도록 하자!
  • 구형 스타일의 캐스트 보다는 C++ 스타일의 캐스트를 쓰자! 발견하기도 쉽고, 설계자가 어떤 역할을 의도했는지가 더 자세히 드러난다!

💡 구형 스타일 캐스트


📌 C 스타일 캐스트

  • (int)fNumber;

📌 함수 방식 캐스트

  • int(fNumber);


💡 신형 스타일 캐스트(C++ 스타일 캐스트)


📌 const_cast

  • 객체의 상수성을 없에는 용도로 사용
    - 해당 기능을 제공하는 캐스트 연산자는 const_cast가 유일

📌 dynamic_cast

  • '안전한 다운캐스팅'을 할 때 사용
    - 주어진 객체가 어떤 클래스 상속 계통에 속한 타입인지 아닌지 결정

  • 구형 스타일 캐스트 문법으로는 흉내조차 낼 수 없음

  • 런타임 비용이 높음

📌 reinterpret_cast

  • 포인터를 int로 바꾸는 등의 하부 수준 캐스팅을 할 때 사용
    - 하부 수준 코드 외에는 사용하지 말아야 함

  • 적용 결과가 구현 환경에 의존적

📌 static_cast

  • 비상수 객체를 상수 객체로 바꾸거나, int를 double로 바꾸는 등의 암시적 변환을 강제로 진행할 때 사용

  • 타입 변환을 거꾸로 수행하는 용도로도 쓰임
    - void*를 일반 타입의 포인터로, 기본 클래스 포인터를 파생 클래스 포인터로 바꾸는 등
    - 상수 객체를 비상수 객체로 바꾸는 건 불가능, 해당 기능은 const_cast 만이 수행 가능



💡 캐스트를 사용할 때...


📌 구형 · 신형 스타일 캐스트, 둘 중 무엇을 쓸까?

  • 신형 스타일 캐스트를 사용하는 것이 바람직 하다!
    - 코드의 가독성이 좋아 어디에서 문제가 생겻는지 파악하기 쉽다
    - 캐스트를 사용한 목적을 더 좁혀서 지정하기 때문에 컴파일러 쪽에서 사용 에러를 진단할 수 있다
    (상수성을 없에려고 할 때, const_cast가 아닌 다른 캐스트 연산자를 사용하면 컴파일 오류 발생)

📢 구형 스타일 캐스트를 쓰는 경우!

class Widget
{
public:
	explicit Widget(int size);
    ...
};

void doSomeWork(const Widget& w);

doSomeWork(Widget(15));						// 구형 스타일 캐스트 사용
doSomeWork(static_cast<Widget>(15));		// 신형 스타일 캐스트 사용
  • 객체를 인자로 받는 함수에 객체를 넘기기 위해 명시호출 생성자를 호출 하고 싶은 경우에 사용
    - 객체를 생성한다고 하면 캐스팅과는 다른 느낌이 드는...
    - 위 예시의 경우 저자의 개인적인 생각이므로 웬만하면 신형 스타일 캐스트를 쓰자!

📌 보기엔 맞지만 실제로는 틀린 코드...

class Window
{
public:
	virtual void onResize() { ... }
    ...
};

class SpecialWindow : public Window
{
public:
	virtual void onResize()
    {
    	static_cast<Window>(*this).onResize();
        ...
    }
    ...
};
  • 상속 구조에서 파생 캘르새의 onResize()에서 키본클래스의 onResize()함수를 불러야 하는 상황...
    - 캐스팅이 일어나며 this*의 기본 클래스 부분에 대한 사본이 임시적으로 만들어지게 되는데, 이 때 현재 객체의 기본 클래스가 아닌, 임시객체의 함수 호출이 이루어진다
    - 위 동작이 끝나면 남은 동작들은 또 현재의 객체에서 동작...

📢 해결 방법

  • 캐스트를 빼야 한다!
class SpecialWindow : public Window
{
public:
	virtual void onResize()
    {
    	Window::onResize();
        ...
    }
    ...
};


💡 말도 많고, 탈도 많은 dynamic_cast


📌 설계적 문제

  • 어떤 구현환경에서는 클래스 이름에 대한 문자열 비교 연산에 기반을 두어 dynamic_cast가 만들졌다
    - 깊이가 4인 상속 계통에서 strcmp가 4번 불린다, 깊이가 더 깊거나, 다중 상속이면...?

  • 대부분의 구현환경의 경우 동작 속도 측면만 생각해도 무방하다!
    - 그래도 수행 성능에 사활이 걸린 코드라면 특히 dynamic_cast에 주의를 놓지 말자!

📌 그럼에도 dynamic_cast를 써야하는 순간 생각해 볼 방법들

파생 클래스 객체임이 분명한 객체에 대해 파생 클래스 함수를 호출 하고 싶은데, 조작할 수 있는 수단이 기본 클래스의 포인터, 참조자 밖에 없을 때...

  • 파생 클래스 객체에 대한 포인터를 컨테이너에 담아 두어, 기본 클래스 인터페이스를 통해 조작할 일을 없에는 방법
class Window { ... };

class SpecialWindow : public Window
{
public:
	void blink();
    ...
};

typedef
vector<shared_ptr<SpecialWindow>> VPSW;

VPSW winPtrs;
...
for (VPSW::iterator iter = winPtrs.begi(); iter != winPtrs.end(); ++iter)
{
	(*iter)->blink();
}

- 해당 방법은 파생 클래스 모두를 위한 각각의 컨테이너가 만들어져야 하는 단점이 있다.

  • 원하는 조작을 가상 함수 집합으로 정리해 기본 클래스에 넣는 방법
class Window 
{
public:
	virtual void blink() {}			// 구현 부를 비워둔다.
};

class SpecialWindow : public Window
{
public:
	virtual void blink();
    ...
};

typedef
vector<shared_ptr<Window>> VPW;

VPW winPtrs;
...
for (VPSW::iterator iter = winPtrs.begi(); iter != winPtrs.end(); ++iter)
{
	(*iter)->blink();
}

📢 두 방법 모두 어떤 상황이든 간에 다 적용하긴 불가능 하지만 알아만 둔다면 적용 할 수 있는 상황에 많은 도움이 된다!

📌 정말 피해야 하는 '폭포식 dynamic_cast'

class Window { ... };

...

typedef
vector<shared_ptr<Window>> VPW;

VPW winPtrs;

...

for (VPSW::iterator iter = winPtrs.begi(); iter != winPtrs.end(); ++iter)
{
	if (SpecialWindow1* psw1 = dynamic_cast<SpecialWindow1*>(iter->get()))
    {
    	...
    }
    else if (SpecialWindow2* psw2 = dynamic_cast<SpecialWindow2*>(iter->get()))
    {
    	...
    }
    else if (SpecialWindow3* psw3 = dynamic_cast<SpecialWindow3*>(iter->get()))
    {
    	...
    }
    ...
}
  • 코드 블럭이 크기만 하고, 속도도 둔할 뿐더러 망가지기 쉬운 코드

  • 클래스의 상속 계통이 조금이라도 바뀔 때 마다 검토 및 수정이 필요해짐

profile
오코완~😤😤

0개의 댓글

관련 채용 정보