C ++은 예외를 더 자주 사용하는 것을 선호합니다.
C ++ 표준 라이브러리는 일반적으로 가장 일반적인 경우 디자인 형태 ( operator[]
예 :) 또는 랜덤 액세스 시퀀스의 범위를 벗어난 액세스와 같은 프로그래머 오류를 발생시키지 않기 때문에 실제로 어떤 측면에서 Objective-C보다 적을 것을 제안합니다. 잘못된 반복자를 역 참조하려고합니다. 언어는 범위를 벗어난 배열에 액세스하거나 null 포인터 또는 이와 같은 것을 참조하지 않습니다.
예외 처리 방정식에서 프로그래머의 실수를 크게 받아들이면 실제로 다른 언어가 자주 응답하는 매우 큰 범주의 오류를 제거합니다 throwing
. C ++은 assert
(릴리스 / 프로덕션 빌드에서 컴파일되지 않고 디버그 빌드 만) 경향이 있거나 그러한 경우 글리치 아웃 (종종 충돌)하는 경향이 있습니다. 언어가 그러한 런타임 검사 비용을 부과하지 않기 때문에 부분적으로 프로그래머가 구체적으로 이러한 검사를 수행하는 코드를 작성하여 비용을 지불하기를 원하지 않는 한 이러한 프로그래머 실수를 감지하는 데 필요한 것입니다.
Sutter는 C ++ 코딩 표준과 같은 경우 예외를 피하도록 권장합니다.
예외를 사용하여 프로그래밍 오류를보고 할 때의 주요 단점은 디버거가 위반이 감지 된 정확한 라인에서 라인 상태를 그대로 유지하면서 디버거를 시작하려는 경우 실제로 스택 해제가 발생하지 않게한다는 것입니다. 요약 : 발생할 수있는 오류가 있습니다 (항목 69-75 참조). 해서는 안되는 모든 것에는 프로그래머의 잘못이 assert
있습니다.
그 규칙은 반드시 돌로 설정되지는 않습니다. 좀 더 미션 크리티컬 한 경우에는 프로그래머 실수가 발생하는 위치와 throw
유효하지 않은 무언가를 연기하거나 한계를 벗어난 액세스와 같은 프로그래머 실수가있는 경우를 일관되게 기록하는 래퍼 및 코딩 표준을 사용하는 것이 바람직 할 수 있습니다. 이 경우 소프트웨어에 기회가있는 경우 복구에 실패하면 비용이 너무 많이들 수 있습니다. 그러나 전반적으로 더 일반적인 언어 사용은 프로그래머 실수에 직면하지 않는 편이 좋습니다.
외부 예외
예를 들어 표준위원회에 따르면 C ++에서 가장 많이 권장되는 예외는 프로그램 외부의 일부 외부 소스에서 예기치 않은 결과와 같이 "외부 예외"에 대한 것입니다. 예를 들어 메모리를 할당하지 못했습니다. 다른 소프트웨어가 소프트웨어를 실행하는 데 필요한 중요한 파일을 열지 못했습니다. 다른 서버가 필요한 서버에 연결하지 못했습니다. 다른 하나는 사용자가 중단 버튼을 방해하여 일반적인 경우 실행 경로가이 외부 중단없이 성공할 것으로 예상되는 작업을 취소하는 것입니다. 이 모든 것은 즉각적인 소프트웨어와 소프트웨어를 작성한 프로그래머의 통제를 벗어납니다. 외부 소스에서 예기치 않은 결과로 인해 작업 (실제로 필자의 책에서 불가분의 거래라고 생각해야 함)이 성공하지 못합니다.
업무
try
트랜잭션은 전체적으로 성공하거나 전체적으로 실패해야하기 때문에 블록을 "트랜잭션"으로 보는 것이 좋습니다 . 우리가 무언가를 시도하고 반쯤 실패하면 일반적으로 트랜잭션이 전혀 실행되지 않은 것처럼 시스템을 유효한 상태로 되돌리려면 프로그램 상태에 대한 부작용 / 돌연변이를 롤백해야합니다. 반쯤 쿼리를 처리하지 못하는 RDBMS가 데이터베이스의 무결성을 손상시키지 않아야합니다. 해당 트랜잭션에서 프로그램 상태를 직접 변경하는 경우 오류 발생시이를 "변경 취소"해야합니다 (여기서 범위 가드는 RAII에 유용 할 수 있습니다).
훨씬 간단한 대안은 원래 프로그램 상태를 변경하지 않습니다 . 사본을 변경 한 다음 성공하면 사본을 원본으로 교체하십시오 (스왑이 발생하지 않도록 보장). 실패하면 사본을 폐기하십시오. 일반적으로 오류 처리에 예외를 사용하지 않는 경우에도 적용됩니다. "트랜잭션"사고 방식은 오류가 발생하기 전에 프로그램 상태 돌연변이가 발생한 경우 적절한 복구의 열쇠입니다. 그것은 전체적으로 성공하거나 전체적으로 실패합니다. 그것의 돌연변이를 만드는 데 반쯤 성공하지는 않습니다.
프로그래머가 오류 또는 예외 처리를 올바르게 수행하는 방법을 묻는 것을 볼 때 가장 자주 논의되는 주제 중 하나입니다. 그 운영. 순도 및 불변성은 스레드 안전성에 도움이되는만큼 예외 안전성을 달성하는 데 도움이 될 수 있습니다. 발생하지 않는 돌연변이 / 외부 부작용은 롤백 할 필요가 없습니다.
공연
예외를 사용할지 여부에 대한 또 다른 지침 요소는 성능이며, 강박 관념, 페니 핀칭, 반 생산적 방법을 의미하지는 않습니다. 많은 C ++ 컴파일러가 "제로 비용 예외 처리"를 구현합니다.
오류없는 실행을 위해 런타임 오버 헤드가 0이므로 C 리턴 값 오류 처리보다 훨씬 뛰어납니다. 트레이드 오프로서 예외의 전파에는 많은 오버 헤드가 있습니다.
내가 읽은 것에 따르면, 일반적인 경로 실행 경로에는 오버 헤드 (일반적으로 C 스타일 오류 코드 처리 및 전파와 함께 발생하는 오버 헤드)가 필요하지 않습니다. 이는 throwing
그 어느 때보 다 비싸다는 의미 입니다.
"고가"는 정량화하기가 다소 어렵지만, 초보자에게는 아마도 꽉 찬 루프에서 백만 번 던지기를 원하지 않을 것입니다. 이러한 종류의 디자인은 예외가 항상 좌우로 발생하지 않는다고 가정합니다.
비 오류
그리고 그 성능 포인트는 오류가 아닌 것으로 나타났습니다. 우리가 모든 종류의 다른 언어를 보면 놀랍게도 모호합니다. 그러나 위에서 언급 한 제로 비용 EH 설계를 감안할 때 throw
세트에서 찾을 수없는 키에 대한 응답으로 거의 원하지 않습니다. 논란의 여지가없는 오류 일뿐 아니라 (키를 검색하는 사용자가 세트를 빌드하고 항상 존재하지 않는 키를 검색 할 것으로 예상 할 수 있음), 그 맥락에서 막대한 비용이 소요될 수 있습니다.
예를 들어, 집합 교차 함수는 두 집합을 반복하고 공통 키를 검색하려고 할 수 있습니다. 키를 찾지 못하면 threw
루프를 반복하고 절반 이상의 반복에서 예외가 발생할 수 있습니다.
Set<int> set_intersection(const Set<int>& a, const Set<int>& b)
{
Set<int> intersection;
for (int key: a)
{
try
{
b.find(key);
intersection.insert(other_key);
}
catch (const KeyNotFoundException&)
{
// Do nothing.
}
}
return intersection;
}
위의 예제는 절대적으로 말도 안되고 과장되었지만, 프로덕션 코드에서 일부 사람들은 C ++에서 예외를 사용하여 다른 언어를 사용하는 것을 보았습니다. 이것은 예외를 적절하게 사용하지 않는다는 합리적으로 실용적인 진술이라고 생각합니다 C ++에서. 위의 또 다른 힌트는 catch
블록이 절대로 할 일이 없으며 그러한 예외를 강제로 무시하기 위해 작성되었다는 것입니다. 보통 C ++에서 예외가 매우 적절하게 사용되지 않는다는 힌트입니다 (보증자는 아니지만).
이러한 유형의 경우 실패를 나타내는 일부 유형의 반환 값 ( false
잘못된 반복자 또는 nullptr
컨텍스트에서 의미가있는 것)은 일반적으로 훨씬 더 적합하며, 오류가 아닌 유형이기 때문에 종종 더 실용적이고 생산적입니다. case는 일반적으로 비슷한 catch
사이트 에 도달하기 위해 스택 해제 프로세스를 요구하지 않습니다 .
질문
예외를 피하려면 내부 오류 플래그를 사용해야합니다. 처리하기가 너무 귀찮습니까, 아니면 예외보다 더 잘 작동합니까? 두 경우를 비교하는 것이 가장 좋습니다.
C ++에서 예외를 피하는 것은 임베디드 시스템이나 사용을 금지하는 특정 유형의 경우 (모든 경우를 피하기 위해 길을 벗어나야하는 경우가 아니라면)에 비생산적 인 것처럼 보입니다. 그렇지 않으면 throw
엄격하게 사용하는 것과 같은 라이브러리 및 언어 기능 nothrow
new
.
어떤 이유로 든 예외를 피해야한다면 (예 : C API를 내보내는 모듈의 C API 경계에서 작업) 많은 사람들이 동의하지 않을 수 있지만 실제로 OpenGL과 같은 전역 오류 처리기 / 상태를 사용하는 것이 좋습니다 glGetError()
. 스레드 로컬 스토리지를 사용하여 스레드 당 고유 한 오류 상태를 가질 수 있습니다.
그 이유는 프로덕션 환경에서 팀이 오류 코드가 반환 될 때 불행히도 가능한 모든 오류를 철저히 확인하는 데 익숙하지 않다는 것입니다. 철저한 경우 일부 C API는 거의 모든 단일 C API 호출에서 오류가 발생할 수 있으며 철저한 검사에는 다음과 같은 것이 필요합니다.
if ((err = ApiCall(...)) != success)
{
// Handle error
}
... 이러한 검사가 필요한 API를 호출하는 거의 모든 단일 코드 행. 그러나 나는 철저한 팀과 함께 일할 운명이 없었습니다. 그들은 종종 이러한 오류를 절반, 때로는 대부분의 경우 무시합니다. 그것은 예외에 대한 가장 큰 매력입니다. 이 API를 래핑 throw
하고 오류가 발생 했을 때 균일하게 만들면 예외를 무시할 수 없으며 내 견해와 경험상 예외의 우수성이 있습니다.
그러나 예외를 사용할 수없는 경우 전역 스레드 당 오류 상태는 적어도 오류 코드를 나에게 반환하는 것과 비교할 때 적어도 이전 오류를 잡을 수있는 이점이 있습니다. 엉뚱한 코드베이스에서 잃어버린 대신 발생하여 어떤 일이 있었는지 완전히 잊어 버렸습니다. 오류가 발생하기 전 또는 이전 함수 호출에서 몇 줄이 발생했을 수 있지만 소프트웨어가 아직 충돌하지 않은 경우 뒤로 이동하여 오류가 발생한 위치와 이유를 파악할 수 있습니다.
포인터가 드물기 때문에 예외를 피하려면 내부 오류 플래그를 사용해야 할 것 같습니다.
포인터가 드물다고 말할 필요는 없습니다. C ++ 11 이후에는 컨테이너의 기본 데이터 포인터와 새로운 nullptr
키워드 를 얻는 방법도 있습니다 . 예외가있을 때 RAII를 준수하는 것이 얼마나 중요한지 대신에 비슷한 것을 사용할 수 있다면 원시 포인터를 사용하여 메모리 를 소유 / 관리 하는 것이 현명하지 않은 것으로 간주됩니다 unique_ptr
. 그러나 메모리를 소유하지 않거나 관리하지 않는 원시 포인터는 (Sutter 및 Stroustrup과 같은 사람들로부터조차) 그렇게 나쁘게 간주되지 않으며 때로는 물건을 가리키는 방법 (물건을 가리키는 인덱스와 함께)으로 매우 실용적인 것으로 간주되지는 않습니다.
그것들은 무효화 된 후에 그것들을 역 참조하려고 시도하는지 감지하지 않는 표준 컨테이너 반복자 (적어도 릴리스에서는 확인 된 반복자가 없음)보다 안전하지 않을 것입니다. C ++은 여전히 부끄럽게도 위험한 언어입니다. 특별히 사용하면 모든 것을 감싸고 소유하지 않은 원시 포인터조차 숨기고 싶지 않다면 말하고 싶습니다. 리소스는 RAII (일반적으로 런타임 비용 없음)를 준수한다는 점을 제외하고는 매우 중요하지만, 개발자가 명시 적으로 원하지 않는 비용을 피하기 위해 반드시 가장 안전한 언어를 사용하려는 것은 아닙니다. 다른 것을 교환하십시오. 권장되는 사용법은 매달려있는 포인터 및 무효화 된 반복자와 같은 것들로부터 당신을 보호하려고하지 않기 때문에 (그렇지 않으면 우리는 사용하도록 권장됩니다)shared_ptr
Stroustrup이 격렬하게 반대하는 모든 곳에서). 리소스가 제대로 해제 / 해제 / 파기 / 잠금 해제 / 정리되지 않을 때 보호하려고합니다 throws
.