흥미로운 답변 : (지금까지) 모두 동의하지만, 지금까지 완전히 무시 된이 질문에 대한 가능한 함축적 의미가 있습니다.
위의 간단한 예를 리소스 할당으로 확장 한 다음 잠재적 인 리소스 해제로 오류 검사를 수행하면 그림이 변경 될 수 있습니다.
초보자가 취할 수 있는 순진한 접근 방식을 고려하십시오 .
int func(..some parameters...) {
res_a a = allocate_resource_a();
if (!a) {
return 1;
}
res_b b = allocate_resource_b();
if (!b) {
free_resource_a(a);
return 2;
}
res_c c = allocate_resource_c();
if (!c) {
free_resource_b(b);
free_resource_a(a);
return 3;
}
do_work();
free_resource_c(c);
free_resource_b(b);
free_resource_a(a);
return 0;
}
위의 내용은 조기 복귀 스타일의 극단적 인 버전을 나타냅니다. 코드가 복잡 해짐에 따라 시간이 지남에 따라 코드가 매우 반복적이고 유지 관리가 불가능 해집니다. 요즘 사람들은 예외 처리 를 사용 하여이를 포착 할 수 있습니다 .
int func(..some parameters...) {
res_a a;
res_b b;
res_c c;
try {
a = allocate_resource_a(); # throws ExceptionResA
b = allocate_resource_b(); # throws ExceptionResB
c = allocate_resource_c(); # throws ExceptionResC
do_work();
}
catch (ExceptionBase e) {
# Could use type of e here to distinguish and
# use different catch phrases here
# class ExceptionBase must be base class of ExceptionResA/B/C
if (c) free_resource_c(c);
if (b) free_resource_b(b);
if (a) free_resource_a(a);
throw e
}
return 0;
}
Philip은 아래의 goto 예제를 살펴본 후 위의 캐치 블록 내부에 끊김없는 스위치 / 케이스 를 사용할 것을 제안했습니다 . 하나는 switch (typeof (e)) 다음 free_resourcex()
호출을 통과 할 수 있지만 이것은 사소한 것이 아니며 디자인 고려가 필요합니다 . 그리고 끊김없는 스위치 / 케이스는 아래에 데이지 체인 레이블이있는 goto와 똑같다는 것을 기억하십시오.
Mark B가 지적했듯이 C ++에서는 Resource Aquisition is Initialization 원칙, 간단히 말해서 RAII 를 따르는 것이 좋은 스타일로 간주됩니다 . 개념의 요점은 개체 인스턴스화를 사용하여 리소스를 획득하는 것입니다. 그런 다음 개체가 범위를 벗어나 해당 소멸자가 호출되는 즉시 리소스가 자동으로 해제됩니다. 상호 의존적 인 리소스의 경우 올바른 할당 해제 순서를 보장하고 모든 소멸자에 필요한 데이터를 사용할 수 있도록 개체 유형을 설계하기 위해 특별한주의를 기울여야합니다.
또는 사전 예외 일에 다음을 수행 할 수 있습니다.
int func(..some parameters...) {
res_a a = allocate_resource_a();
res_b b = allocate_resource_b();
res_c c = allocate_resource_c();
if (a && b && c) {
do_work();
}
if (c) free_resource_c(c);
if (b) free_resource_b(b);
if (a) free_resource_a(a);
return 0;
}
그러나이 지나치게 단순화 된 예제에는 몇 가지 단점이 있습니다. 할당 된 리소스가 서로 의존하지 않는 경우에만 사용할 수 있습니다 (예 : 메모리를 할당 한 다음 파일 핸들을 연 다음 핸들에서 메모리로 데이터를 읽는 데 사용할 수 없음). ), 그리고 반환 값으로 구별 가능한 개별 오류 코드를 제공하지 않습니다.
코드를 빠르게 (!) 유지하기 위해 간결하고 쉽게 읽을 수 있고 확장 할 수있는 Linus Torvalds는 악명 높은 goto 를 절대적으로 합당한 방식으로 사용하더라도 리소스를 다루는 커널 코드에 대해 다른 스타일을 적용했습니다 .
int func(..some parameters...) {
res_a a;
res_b b;
res_c c;
a = allocate_resource_a() || goto error_a;
b = allocate_resource_b() || goto error_b;
c = allocate_resource_c() || goto error_c;
do_work();
error_c:
free_resource_c(c);
error_b:
free_resource_b(b);
error_a:
free_resource_a(a);
return 0;
}
커널 메일 링리스트에 대한 논의의 요점은 goto 문보다 "선호되는"대부분의 언어 기능이 거대한, 트리와 같은 if / else, 예외 처리기, 루프 / 중단 / 계속 문 등과 같은 암시 적 gotos라는 것입니다. 그리고 위의 예에서 goto는 작은 거리 만 점프하고 명확한 레이블을 가지고 있으며 오류 조건을 추적하기 위해 다른 코드를 제거하기 때문에 괜찮은 것으로 간주됩니다. 이 질문은 여기서 stackoverflow에서 논의되었습니다 .
그러나 마지막 예제에서 누락 된 것은 오류 코드를 반환하는 좋은 방법입니다. result_code++
각 free_resource_x()
호출 후에 를 추가하고 해당 코드를 반환 하려고 생각 했지만 이로 인해 위 코딩 스타일의 속도 향상이 일부 상쇄됩니다. 그리고 성공한 경우 0을 반환하기가 어렵습니다. 어쩌면 나는 상상력이 부족할지도 모릅니다 ;-)
그래서, 예, 저는 조기 수익을 코딩하는 문제에 큰 차이가 있다고 생각합니다. 그러나 컴파일러를 위해 재구성하고 최적화하는 것이 더 어렵거나 불가능한 더 복잡한 코드에서만 분명하다고 생각합니다. 일반적으로 자원 할당이 시작되면 일반적으로 발생합니다.