C ++에서 오류 처리를위한 try-catch 또는 ifs


30

예외는 게임 엔진 디자인에 널리 사용됩니까, 아니면 순수한 if 문을 사용하는 것이 더 바람직합니까? 예를 들면 다음과 같습니다.

try {
    m_fpsTextId = m_statistics->createText( "FPS: 0", 16, 20, 20, 1.0f, 1.0f, 1.0f );
    m_cpuTextId = m_statistics->createText( "CPU: 0%", 16, 20, 40, 1.0f, 1.0f, 1.0f );
    m_frameTimeTextId = m_statistics->createText( "Frame time: 0", 20, 20, 60, 1.0f, 1.0f, 1.0f );
    m_mouseCoordTextId = m_statistics->createText( "Mouse: (0, 0)", 20, 20, 80, 1.0f, 1.0f, 1.0f );
    m_cameraPosTextId = m_statistics->createText( "Camera pos: (0, 0, 0)", 40, 20, 100, 1.0f, 1.0f, 1.0f );
    m_cameraRotTextId = m_statistics->createText( "Camera rot: (0, 0, 0)", 40, 20, 120, 1.0f, 1.0f, 1.0f );
} catch ... {
    // some info about error
}

그리고 ifs와 함께 :

m_fpsTextId = m_statistics->createText( "FPS: 0", 16, 20, 20, 1.0f, 1.0f, 1.0f );
if( m_fpsTextId == -1 ) {
    // show error
}
m_cpuTextId = m_statistics->createText( "CPU: 0%", 16, 20, 40, 1.0f, 1.0f, 1.0f );
if( m_cpuTextId == -1 ) {
    // show error
}
m_frameTimeTextId = m_statistics->createText( "Frame time: 0", 20, 20, 60, 1.0f, 1.0f, 1.0f );
if( m_frameTimeTextId == -1 ) {
    // show error
}
m_mouseCoordTextId = m_statistics->createText( "Mouse: (0, 0)", 20, 20, 80, 1.0f, 1.0f, 1.0f );
if( m_mouseCoordTextId == -1 ) {
    // show error
}
m_cameraPosTextId = m_statistics->createText( "Camera pos: (0, 0, 0)", 40, 20, 100, 1.0f, 1.0f, 1.0f );
if( m_cameraPosTextId == -1 ) {
    // show error
}
m_cameraRotTextId = m_statistics->createText( "Camera rot: (0, 0, 0)", 40, 20, 120, 1.0f, 1.0f, 1.0f );
if( m_cameraRotTextId == -1 ) {
    // show error
}

예외는 ifs보다 약간 느리다는 것을 들었고 if-checking 방법과 예외를 혼합해서는 안됩니다. 그러나 예외적으로 모든 initialize () 메소드 또는 유사한 것 후에 ifs 톤보다 더 읽기 쉬운 코드가 있지만 때로는 내 의견으로는 하나의 메소드에 비해 너무 무겁습니다. 그들은 게임 개발에 좋습니까, 아니면 간단한 if를 사용하는 것이 더 좋습니까?


많은 사람들은 Boost가 게임 개발에 좋지 않다고 주장하지만 [ "Boost.optional"] ( boost.org/doc/libs/1_52_0/libs/optional/doc/html/index.html ) 을 살펴 보는 것이 좋습니다. 당신의 IFS 예를 좀 더 좋은
ManicQin

Andrei Alexandrescu의 오류 처리에 대한 좋은 이야기 가 있습니다. 예외없이 비슷한 접근 방식을 사용했습니다.
Thelvyn

답변:


48

짧은 대답 : Niklas Frykholm의 Sensible Error Handling 1 , Sensible Error Handling 2Sensible Error Handling 3 을 읽으십시오 . 실제로, 블로그에있는 동안 기사를 모두 읽으십시오. 내가 모든 것에 동의한다고 말하지는 않지만 대부분 금입니다.

예외를 사용하지 마십시오. 많은 이유가 있습니다. 나는 주요한 것들을 열거 할 것이다.

최신 컴파일러에서는 상당히 최소화되었지만 실제로 느려질 수 있습니다. 일부 컴파일러는 실제로 예외를 트리거하지 않는 코드 경로에 대해 "제로 오버 헤드 예외 없음"을 지원합니다 (예외 처리에 필요한 추가 데이터가 여전히 있기 때문에 실행 파일 / dll 크기가 부풀어 오름). 그러나 결과는 예외를 사용하는 것이 느리고 성능에 중요한 코드 경로에서는 예외를 피해야한다는 것입니다. 컴파일러에 따라 전혀 활성화하면 오버 헤드가 추가 될 수 있습니다. 대부분의 경우 항상 코드 크기를 크게 늘려 오늘날 하드웨어의 성능에 심각한 영향을 줄 수 있습니다.

예외는 코드를 훨씬 더 취약 하게 만듭니다 . 기본적으로 예외 안전 코드와 예외 안전하지 않은 코드를 작성하는 데 어려움이 차트에 표시되는 악명 높은 그래픽 (지금 슬프게도 찾을 수 없음)이 있으며, 전자는 상당히 큰 막대입니다. 예외 작은 개는 많은, 많은 단지가 있습니다 많은 코드를 작성하는 방법을 보이는 예외 안전하지만 정말 아니다는. 전체 C ++ 11위원회 조차도 이것에 대해 다루었 고 std :: unique_ptr을 올바르게 사용하기위한 중요한 도우미 함수를 추가하는 것을 잊어 버렸습니다. 이러한 도우미 함수조차도 사용하지 않는 것보다 더 많은 타이핑이 필요하며 대부분의 프로그래머는 그렇지 않은 경우 무엇이 잘못되었는지조차 깨닫지 못합니다.

보다 구체적으로 게임 산업에서 일부 콘솔의 공급 업체 제공 컴파일러 / 런타임은 예외를 완전히 지원하지 않거나 전혀 지원하지도 않습니다. 코드에서 예외를 사용하는 경우에도 여전히이 시대에 코드를 새로운 플랫폼으로 이식하기 위해 코드의 일부를 다시 작성해야 할 수도 있습니다. (콘솔이 출시 된 후 7 년 동안 변경되었는지 확실하지 않다; 우리는 예외를 사용하지 않고 컴파일러 설정에서도 예외를 비활성화했기 때문에 내가 말한 사람이 최근에 확인한 적이 있다는 것을 모른다.)

일반적인 사고 방식은 매우 분명합니다. 예외적 인 상황 에서는 예외를 사용하십시오 . 당신의 프로그램이 "어떻게해야할지 모르겠다. 다른 누군가가 뭘할지 모르겠다. 그래서 예외를 던지고 어떤 일이 일어나는지 볼 것이다." 다른 옵션이 의미가 없을 때 사용하십시오. 적절한 스마트 핸들 사용을 망쳐 서 실수로 약간의 메모리가 누출되거나 리소스를 정리하지 못하는 경우 걱정하지 않을 때 사용하십시오. 다른 모든 경우에는 사용하지 마십시오.

예제와 같은 코드와 관련하여 문제를 해결하는 몇 가지 다른 방법이 있습니다. 간단한 예에서 가장 이상적인 것은 아니지만 더 강력한 방법 중 하나는 모나드 오류 유형에 의존하는 것입니다. 즉, createText ()는 정수가 아닌 사용자 정의 핸들 유형을 리턴 할 수 있습니다. 이 핸들 유형에는 텍스트를 업데이트하거나 제어하기위한 접근자가 있습니다. 핸들이 오류 상태 (createText ()가 실패했기 때문에)에 놓이면 핸들에 대한 추가 호출은 자동으로 실패합니다. 핸들을 쿼리하여 오류가 있는지 확인하고, 그렇다면 오류가 무엇인지 확인할 수 있습니다. 이 방법은 다른 옵션보다 오버 헤드가 많지만 상당히 견고합니다. 프로덕션에서 단일 작업이 실패 할 수 있지만 실패 / 불가 / 실적이없는 상황에서 긴 작업 문자열을 수행해야하는 경우에 사용하십시오.

모나드 오류 처리를 구현하는 대안은 사용자 정의 핸들 객체를 사용하는 대신 컨텍스트 객체의 메소드가 유효하지 않은 핸들 ID를 정상적으로 처리하도록하는 것입니다. 예를 들어, createText ()가 실패했을 때 -1을 반환하면, 그 핸들 중 하나를 취하는 m_statistics에 대한 다른 호출은 -1이 전달되면 정상적으로 종료되어야합니다.

실제로 오류가 발생한 함수에 오류 인쇄를 넣을 수도 있습니다. 귀하의 예에서 createText ()는 무엇이 잘못되었는지에 대한 자세한 정보를 가지고 있으므로 로그에 더 의미있는 오류를 덤프 할 수 있습니다. 이 경우 오류 처리 / 인쇄를 발신자에게 푸시하는 것이 별 도움이되지 않습니다. 호출자가 처리를 사용자 정의해야하거나 종속성 주입을 사용해야 할 때 수행하십시오. 오류가 기록 될 때마다 팝업 할 수있는 게임 내 콘솔을 사용하는 것이 좋습니다.

통계 시스템에 텍스트 블롭을 생성하는 간단한 동작과 같이 정상적인 환경에서 실패하지 않을 것으로 예상되는 통화에 대한 최상의 옵션 (위의 링크 된 기사에 이미 나와 있음)은 기능 만 갖는 것입니다. 실패했습니다 (예제에서 createText) 중단하십시오. 무언가 완전히 손상되지 않은 경우 (예 : 사용자가 글꼴 데이터 파일을 삭제했거나 어떤 이유로 256MB의 메모리 만있는 경우 등) 프로덕션 텍스트에서 createText ()가 실패하지 않을 것이라고 합리적으로 확신 할 수 있습니다. 이러한 많은 경우에 실패가 발생했을 때해야 할 일조차 없습니다. 메모리가 부족합니까? 사용자에게 OOM 오류를 표시하는 멋진 GUI 패널을 작성하는 데 필요한 할당을 수행하지 못할 수도 있습니다. 글꼴이 없습니까? 사용자에게 오류를 표시하기 어렵게 만듭니다. 무슨 일이 있어도

(a) 오류를 로그 파일에 기록하고 (b) 일반 사용자 작업으로 인해 발생하지 않는 오류에 대해서만 오류가 발생하는 한 충돌은 아주 좋습니다.

가용성이 중요하고 워치 독 모니터링이 충분하지 않지만 게임 클라이언트 개발 과는 다른 많은 서버 응용 프로그램에 대해 전혀 같은 말을하지 않습니다 . 다른 언어의 예외 처리 기능은 관리되는 환경이므로 C ++의 예외 안전 문제가 모두 없기 때문에 C ++처럼 물지 않는 경향이 있으므로 C / C ++를 사용하지 않는 것이 좋습니다. 서버가 게임 클라이언트와 같은 최소 지연 시간 보장보다 병렬 처리 및 처리량에 더 집중하는 경향이 있기 때문에 성능 문제도 완화됩니다. 예를 들어 C #으로 작성된 슈팅 게임 등을위한 액션 게임 서버조차도 FPS 클라이언트가하는 것처럼 하드웨어를 한계까지 밀기 어렵 기 때문에 C #으로 작성할 때 매우 잘 작동 할 수 있습니다.


1
+1. 또한 warn_unused_result처리되지 않은 오류 코드를 포착 할 수있는 일부 컴파일러에서 함수 와 같은 속성을 추가 할 수 있습니다.
Maciej Piechotka

제목에 C ++이 보였고 모나 딕 오류 처리가 아직 여기에 없을 것이라고 확신했습니다. 좋은 물건!
Adam

성능이 중요하지 않고 대부분 빠른 구현을 목표로하는 장소는 어떻습니까? 예를 들어 게임 로직을 구현할 때?
Ali1S232

또한 디버깅 목적으로 만 예외 처리를 사용한다는 아이디어는 어떻습니까? 게임을 출시 할 때와 같이 문제없이 모든 것이 원활하게 진행될 것으로 예상합니다. 따라서 예외를 사용하여 개발 중 버그를 찾아 수정하고 나중에 릴리스 모드에서 해당 예외를 모두 제거하십시오.
Ali1S232

1
@Gajoo : 게임 로직의 경우 예외로 인해 로직을 따르기가 더 어려워집니다 (모든 코드를 따르기가 어려워 짐). Pythnn 및 C #에서도 게임 로직에는 예외가 있습니다. 디버깅을 위해 하드 어설트가 일반적으로 더 편리합니다. 스택의 많은 부분을 풀고 예외 발생 시맨틱으로 인해 모든 종류의 상황 정보를 잃은 후가 아니라 정확한 순간에 무언가를 깨고 멈추십시오. 디버깅 로직의 경우 도구 및 정보 / 편집 GUI를 구축하여 설계자가 검사하고 조정할 수 있습니다.
Sean Middleditch 2012 년

13

도널드 크 누스 자신의 오래된 지혜는 다음과 같습니다.

"우리는 시간의 약 97 %라는 작은 비 효율성을 잊어야합니다. 조기 최적화는 모든 악의 근원입니다."

이 포스터를 큰 포스터로 인쇄하고 진지한 프로그래밍 작업을 수행하는 모든 곳에서 행 아웃하십시오.

그래서 심지어 경우 시도 / 잡기가 조금 느린 나는 기본적으로 그것을 사용합니다 :

  • 코드는 가능한 한 읽기 쉽고 이해하기 쉬워야합니다. 당신은 할 수 한 번 코드를 작성하지만 것이다 개선, 이해를 위해, 다른 코드를 디버깅하기 위해,이 코드를 디버깅 그것에게 더 많은 시간을 읽고 ...

  • 성능에 대한 정확성. if / else를 올바르게 수행하는 것은 쉬운 일이 아닙니다. 귀하의 예에서는 하나의 오류가 표시 될 수 없지만 여러 오류가 표시되기 때문에 잘못 수행되었습니다. 캐스케이드 if / then / else를 사용해야합니다.

  • 일관되게 사용하기 쉬움 : Try / catch는 모든 라인에서 오류를 확인할 필요가없는 스타일을 수용합니다. 반면에 : 하나 다른 / IF 및 코드의 힘 사고의 혼란이 없습니다. 물론 재현 할 수없는 상황에서만.

그래서 마지막 요점 :

예외가 ifs보다 약간 느리다는 것을 들었습니다.

나는 약 15 년 전에 최근에 믿을만한 소식을들은 적이 없다고 들었습니다. 컴파일러가 개선되었거나 무엇이든 가능합니다.

요점은 다음과 같습니다. 조기 최적화를 수행하지 않습니다. 당신이 손에 코드가 일부 많이 사용되는 코드의 꽉 내부 루프는 것을 벤치 마크 증명할 수있는 경우에만 작업을 수행 하고 스타일 스위칭 성능을 크게 향상시킬 수 있음을 상당히 . 이것은 3 % 사례입니다.


22
나는 이것을 무한대로 -1 할 것이다. 이 Knuth 인용문이 개발자의 두뇌에 미친 피해를 추측 할 수는 없습니다. 예외 사용 여부를 조기에 최적화하지 않는 것은 성능 결정을 넘어서는 결과를 가져 오는 중요한 디자인 결정 인 디자인 결정입니다.
sam hocevar

3
@ SamHocevar : 죄송합니다, 당신의 요점을 알 수 없습니다 : 예외를 사용할 때 가능한 성능 저하에 대한 질문 입니다 . 내 요점 : 그것에 대해 생각하지 마십시오, 히트는 그렇게 나쁘지 않으며 (있는 경우) 다른 것들이 훨씬 더 중요합니다. 내리스트에 "디자인 결정"을 추가하여 동의하는 것 같습니다. 승인. 반면에 Knuth의 인용문은 나쁜 것으로 조기 최적화가 나쁘지 않다는 것을 암시합니다. 그러나 이것이 바로 여기서 일어난 일입니다. IMO : Q는 아키텍처, 디자인 또는 다른 알고리즘에 대해서만 생각하지 않으며 예외와 성능 영향에 대해서만 생각합니다.
AH

2
C ++의 명확성에 동의하지 않습니다. C ++에는 검사되지 않은 예외가있는 반면 일부 컴파일러는 검사 된 반환 값을 구현합니다. 결과적으로 코드가 더 명확 해지지 만 숨겨진 메모리 누수가 있거나 코드가 정의되지 않은 상태가 될 수 있습니다. 또한 타사 코드는 예외 안전하지 않을 수 있습니다. (예외를 확인한 Java / C #에서는 상황이 다릅니다. GC, ...). 디자인 결정 시점에서-API 포인트를 교차하지 않으면 각 스타일에서 리팩토링을 펄 1 라이너로 반자동으로 수행 할 수 있습니다.
Maciej Piechotka

1
@SamHocevar : " 질문은 더 빠른 것이 아니라 선호하는 것에 관한 것입니다. "질문의 마지막 단락을 다시 읽으십시오. 그가 예외를 사용하지 않는 것에 대해 생각 하는 유일한 이유는 그들이 느리다고 생각하기 때문입니다. 이제 OP가 고려하지 않은 다른 문제가 있다고 생각되면 자유롭게 게시하거나 관심을 가진 사람들을 찬성하십시오. 그러나 OP는 예외 성과에 매우 명확하게 초점을 맞추고 있습니다.
Nicol Bolas

3
@AH-Knuth를 인용한다면 나머지 부분 (및 IMO가 가장 중요한 부분)을 잊지 마십시오. " 그러나 우리는이 중요한 3 %의 기회를 놓치지 말아야합니다. 그는 이러한 추론을 통해 만족에 빠지면서 중요한 코드를주의 깊게 살펴 보는 것이 현명 할 것이지만 그 코드가 확인 된 후에야 비로소 " 너무나 자주 이것을 최적화하지 않는 것으로 변명하거나 성능이 최적화되지 않았지만 실제로 기본 요구 사항 인 경우 코드를 느리게 정당화하려고 시도합니다.
Maximus Minimus

10

이미이 질문에 대한 훌륭한 답변이 있었지만 특정 경우 코드 가독성 및 오류 복구에 대한 생각을 추가하고 싶습니다.

코드가 실제로 다음과 같이 보일 수 있다고 생각합니다.

m_fpsTextId = m_statistics->createText( "FPS: 0", 16, 20, 20, 1.0f, 1.0f, 1.0f );
m_cpuTextId = m_statistics->createText( "CPU: 0%", 16, 20, 40, 1.0f, 1.0f, 1.0f );
m_frameTimeTextId = m_statistics->createText( "Frame time: 0", 20, 20, 60, 1.0f, 1.0f, 1.0f );
m_mouseCoordTextId = m_statistics->createText( "Mouse: (0, 0)", 20, 20, 80, 1.0f, 1.0f, 1.0f );
m_cameraPosTextId = m_statistics->createText( "Camera pos: (0, 0, 0)", 40, 20, 100, 1.0f, 1.0f, 1.0f );
m_cameraRotTextId = m_statistics->createText( "Camera rot: (0, 0, 0)", 40, 20, 120, 1.0f, 1.0f, 1.0f );

예외도없고 if도 없습니다. 오류보고는에서 수행 할 수 있습니다 createText. 그리고 createText나머지 코드도 마찬가지로 작동하도록 반환 값을 확인할 필요가없는 기본 텍스처 ID를 반환 할 수 있습니다.

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