C ++ 코드에 예외 안전이 얼마나 중요합니까?


19

코드를 강력한 예외 안전으로 만들 때마다 시간이 많이 걸리기 때문에 코드를 작성하지 않는 것이 좋습니다. 이 비교적 간단한 스 니펫을 고려하십시오.

Level::Entity* entity = new Level::Entity();
entity->id = GetNextId();
entity->AddComponent(new Component::Position(x, y));
entity->AddComponent(new Component::Movement());
entity->AddComponent(new Component::Render());
allEntities.push_back(entity); // std::vector
entityById[entity->id] = entity; // std::map
return entity;

기본적인 예외 보장을 구현하기 위해 new호출 에 범위가 지정된 포인터를 사용할 수 있습니다 . 이렇게하면 호출 중 하나라도 예외를 발생시키는 경우 메모리 누수가 방지됩니다.

그러나 강력한 예외 보증을 구현하고 싶다고 가정 해 봅시다. 적어도, 나는 내 컨테이너에 대한 공유 포인터 (내가 부스트를 사용하지 않는)하는 nothrow 구현해야 Entity::Swap원자 구성 요소를 추가하고, 원자 모두에 추가하기위한 관용구 어떤 종류의 VectorMap. 구현하는 데 시간이 오래 걸리고, 안전하지 않은 예외 솔루션보다 훨씬 더 많은 복사가 필요하므로 비용이 많이 듭니다.

궁극적으로 간단한 CreateEntity기능이 예외적으로 안전 하도록 모든 것을하는 데 걸린 시간이 정당화되지는 않는다고 생각 합니다. 어쩌면 게임이 오류를 표시하고 그 시점에서 닫히기를 원할 것입니다.

자신의 게임 프로젝트에서 이것을 얼마나 멀리 가지고 있습니까? 예외가있을 때 충돌 할 수있는 프로그램에 대해 안전하지 않은 예외 코드를 작성하는 것이 일반적으로 허용됩니까?


21
과거에 C ++ 프로젝트에서 일할 때 기본적으로 예외는 존재하지 않는 것으로 나타났습니다.
Tetrad

2
나는 개인적으로 예외를 좋아하고 실제로 내 코드가 어떻게 강력한 보장을 제공하는지 알아내는 것을 종종 즐깁니다. 그래도 예외가 발생했을 때 충돌이 발생하면 ... 방해하지 마십시오.

7
개인적으로 예외를 사용하여 관리합니다. .. '예외'는 오류가 아닙니다. 마찬가지로 함수가 충돌 할 수 있음을 알고 있으면 충돌이 발생하지만 함수가 성공적으로 아카이브를 읽을 수는 없지만 다른 방법이 있다는 것을 알고 있다면 시도, 캐치로 둘러 쌉니다. "읽을 수 없음"예외 인 경우 아카이브를 읽지 않고 다른 방식으로 관리합니다.
구스타보 메이 엘

Gtoknu가 말한 것처럼 외부 라이브러리가 어떤 예외를 던질 수 있는지 알아야합니다.
피터 Ølsted

답변:


26

예외를 사용하려는 경우 (이 질문의 특정 범위를 벗어난 접근법에 대한 장단점이 있습니다) 예외 안전 코드를 올바르게 작성해야합니다.

예외를 사용하지 않고 명시 적으로 예외 안전하지 않은 코드는 예외를 사용하는 코드보다 낫지 만 예외 안전은 절반 정도입니다. 후자의 경우 예외가 발생했을 때 발생할 수있는 버그와 실패의 전체 클래스가 있으므로 전혀 복구 할 수 없으므로 예외의 이점 중 하나가 완전히 무효화됩니다. 상대적으로 드물게 실패한 경우에도 여전히 가능합니다.

예외를 사용하는 경우 모든 코드가 강력한 예외 보증을 제공해야한다는 것은 아닙니다. 각 코드는 해당 코드를 소비 할 수 있도록 일종의 (없음, 기본, 강력한) 보증을 제공해야합니다. 소비자가 어떤 보증을 제공 할 수 있는지 알고 있습니다. 일반적으로 문제의 구성 요소가 낮은 수준 일수록 보증 수준이 높아집니다.

강력한 보장을 제공하지 않거나 예외 처리 패러다임과 그에 따른 모든 추가 작업 및 단점을 실제로 수용하지 않을 경우 실제로 의미하는 모든 이점을 얻지 못하고 더 쉬울 수 있습니다 그냥 예외를 잊어 버린 시간.


12
이것만으로도 +1의 가치가 있습니다. "예외를 사용하지 않고 명시 적으로 예외 안전하지 않은 코드는 예외를 사용하는 코드보다 낫지 만 예외 안전성을 절반으로 높이고 있습니다."
Maximus Minimus

14

내 일반적인 규칙 : 예외를 사용해야하는 경우 예외를 사용하십시오. 예외를 사용할 필요가 없으면 예외를 사용하지 마십시오. 예외 사용 여부를 모르는 경우 예외를 사용할 필요가 없습니다.

항상 중요한 미션 크리티컬 애플리케이션에서 완벽한 최후의 방어선은 예외입니다. 절대 실패 할 수없는 항공 교통 관제 소프트웨어를 작성하는 경우 예외를 사용하십시오. 원자력 발전소의 제어 코드를 작성하는 경우 예외를 사용하십시오.

반면에 게임을 개발할 때는 만트라를 따르는 것이 훨씬 낫습니다. 조기 충돌, 큰 충돌. 문제가있는 경우 가능한 빨리 문제에 대해 알고 싶습니다. 오류가 눈에 보이지 않게 '처리'되기를 원하지 않는 경우가 종종 있습니다. 이는 나중에 잘못된 동작의 실제 원인을 숨기고 필요한 것보다 디버깅을 훨씬 어렵게하기 때문입니다.


5

예외의 문제점은 어쨌든 처리해야한다는 것입니다. 메모리 부족으로 인해 예외가 발생하면 어쨌든 해당 오류를 처리하지 못할 수 있습니다. 오류 상자를 표시하는 하나의 거대한 예외 처리기에서 전체 게임을 덤프 할 수도 있습니다.

게임 개발을 위해 가능한 경우 코드를 자비 롭게 실패하게 만드는 방법을 찾고자합니다.

예를 들어 게임에 특수 ERROR 메시를 하드 코딩합니다. 흰색 X와 흰색 테두리가있는 회전하는 빨간색 원입니다. 게임에 따라 보이지 않는 것이 더 좋습니다. 시작시 초기화 된 단일 인스턴스가 하나 있습니다. 외부 파일을 사용하지 않고 코드에 구워 지므로 실패 할 수 없습니다.

정상적인 loadMesh 호출이 오류를 발생시키지 않고 데스크탑에 충돌하는 대신 실패하면 콘솔 / 로그 파일에 자동으로 실패를 기록하고 하드 코딩 된 오류 메시를 반환합니다. 나는 mod가 물건을 망칠 때 Skyrim이 실제로 같은 일을한다는 것을 알았습니다. 플레이어가 에러 메시를 보지 못할 가능성이 높습니다 (일부 큰 문제가 있고 그 중 많은 것이 그것과 같지 않다면).

폴백 셰이더도 있습니다. 기본 정점 행렬 연산을 수행하지만 어떤 이유로 균일 한 행렬이없는 경우 여전히 직교 뷰를 수행해야합니다. 적절한 음영 / 조명 / 재질이 없습니다 (color_overide 플래그가 있기 때문에 오류 메쉬와 함께 사용할 수 있음).

유일하게 가능한 문제는 오류 메시 버전이 어떤 식 으로든 게임을 영구적으로 중단 할 수있는 경우입니다. 예를 들어 플레이어가 영역을로드하면 오류 메시가 바닥에 떨어질 수 있고 이번에는 올바른 메쉬 작업으로 게임을 다시로드하면 더 큰 경우 물리가 적용되지 않도록합니다. 지상에 포함. 물리 메시를 그래픽 메시와 함께 저장하기 때문에 그렇게 어렵지 않아야합니다. 스크립팅도 문제가 될 수 있습니다. 어떤 것들로 당신은 열심히 죽어야 할 것입니다. 대체 '레벨'을 사용하는 것이 무엇인지 확실하지 않습니다 (오류가 발생했다는 표시가있는 작은 방을 만들 수는 있지만 플레이어가 막히지 않기 때문에 저장이 비활성화되어 있는지 확인하십시오).

'신규'의 경우 게임 개발을 위해 일종의 팩토리를 사용하는 것이 좋습니다. 실제로 객체를 사전에 할당하거나 대량으로 만들기와 같은 다양한 이점을 제공 할 수 있습니다. 또한 메모리 관리에 사용할 수있는 객체의 전역 인덱스와 같은 것을 쉽게 만들어 ID로 물건을 찾을 수 있습니다 (생성자에서도 그렇게 할 수 있음). 물론 할당 오류도 처리 할 수 ​​있습니다.


2

런타임 검사 및 충돌 누락에 대한 가장 큰 문제는 해커가 충돌을 사용하여 프로세스 및 시스템을 대신 할 가능성이 있다는 것입니다.

이제 해커는 충돌이 발생하자마자 프로세스가 쓸모없고 무해합니다. 널 포인터에서 단순히 읽으면 깔끔한 충돌이므로 실수를하고 프로세스가 즉시 죽습니다.

기술적으로 불법적이지 않지만 의도 한 명령이 아닌 명령을 실행하면 해커가 신중하게 조작 된 데이터를 사용하여 해당 작업을 지시 할 수 있습니다.

잡히지 않은 예외는 실제로 실제 충돌이 아니라 단지 프로그램을 종료하는 방법입니다. 예외는 특정 상황에서 예외를 발생시키는 라이브러리를 누군가가 작성했기 때문에 발생합니다. 일반적으로 예상치 못한 상황에 빠지지 않기 위해 해커에게 개방적 일 수 있습니다.

실제로 위험한 움직임은 예외를 미연에 방지하기보다는 예외를 잡아내는 것입니다. 즉, 절대 그렇게해서는 안된다는 말이 아니라, 처리 할 수 ​​있고 나머지는 미끄럼 방지 할 수있는 사람 만 잡아야합니다. 그렇지 않으면 라이브러리에 조심스럽게 넣은 안전 조치를 취소 할 위험이 있습니다.

배열의 끝을 넘어 쓰는 것과 같이 반드시 예외를 발생시키지 않는 오류에 집중하십시오. 당신의 프로그램은 우연히 그렇게 할 수 없었습니까?


2

예외를보고이를 트리거 할 수있는 조건을 봐야합니다. 개발 / 디버그 빌드 / 테스트주기 동안 적절한 점검으로 사람들이 예외를 사용하는 것의 상당 부분을 피할 수 있습니다. 어설 션을 사용하고, 참조를 전달하고, 선언시 항상 로컬 변수를 초기화하고, 모든 할당을 memset-zero로 지정하고, NULL을 해제 한 후 등 수많은 이론적 예외 사례가 사라집니다.

고려하십시오 : 무언가가 실패하고 예외를 던질 후보가 될 수 있다면-그 영향은 무엇입니까? 얼마나 중요합니까? 게임에서 메모리 할당에 실패한 경우 (원본 예를 들면) 일반적으로 실패 사례를 처리하는 방법보다 손에 큰 문제가 있으며 예외를 통해 실패 사례를 처리해도 더 큰 문제는 발생하지 않습니다. 저리가 더 나쁜 것은 실제로 더 큰 문제가 있다는 사실을 숨길 수도 있습니다. 너 저걸 원하니? 나는 그렇지 않다는 것을 안다. 메모리 할당에 실패하면 충돌과 끔찍한 충돌을 일으켜 가능한 한 실패 지점에 가깝게 수행하여 디버거에 침입하여 진행 상황을 파악하고 올바르게 수정한다는 것을 알고 있습니다.


1

SE에서 다른 사람을 인용하는 것이 적절한 지 확실하지 않지만 다른 답변이 실제로 이것에 닿지 않았다고 생각합니다.

그의 책 Game Engine Architecture 에서 Jason Gregory 인용 . (위대한 책!)

"SEH (Structured exception handling)는 프로그램에 많은 오버 헤드를 추가합니다. 모든 스택 프레임은 스택 해제 프로세스에 필요한 추가 정보를 포함하도록 보강되어야합니다. 또한 스택 해제는 일반적으로 2-3 단계 정도 매우 느립니다. 단순히 함수에서 반환하는 것보다 시간이 더 비쌉니다. 또한 프로그램 (또는 라이브러리)의 함수 중 하나라도 SEH를 사용하는 경우 전체 프로그램은 SEH를 사용해야합니다. 컴파일러는 호출 스택에서 어떤 함수가 호출 스택에서 위에있을 수 있는지 알 수 없습니다 당신은 예외를 던졌습니다. "

내가 말한대로, 내가 만들고있는 엔진은 예외를 사용하지 않습니다. 이것은 앞에서 언급 한 잠재적 인 성능 비용과 모든 목적을 위해 오류 반환 코드가 제대로 작동하기 때문입니다. 실제로 장애를 찾아야하는 유일한 시간은 리소스를 초기화하고로드하는 것 뿐이며, 이러한 경우 성공을 나타내는 부울을 반환하는 것이 좋습니다.


2
구조적 예외 처리는 Windows에서 사용 가능한 특정 유형의 예외 처리이며 일반적으로 C ++ 예외와 동일하지 않습니다.
Kylotan

Doh, 나는 이것을 모른다고 믿을 수 없다. 정정 주셔서 감사합니다!
KlashnikovKid

0

나는 예외가 발생하거나 잡힐 때마다 무언가를 기록 할 수 있기 때문에 문제가 발생하는 위치를 쉽게 알 수 있기 때문에 항상 코드 예외를 안전하게 만듭니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.