2011-12-19 1 views
1

RAII를 사용하여 클래스를 구현하고 싶습니다. 생성자에서 리소스를 가져와야하지만 수집이 실패했을 수 있습니다. 나는 다음과 같은 사용하여 파일의 예를주지 :리소스 획득에 실패 할 수있는 경우 RAII를 구현하는 방법

class file { 
public: 
    file(const char* filename) { 
     file_ = fopen(filename, "w+"); 
     if(!file_) { 
      // Okay 
     } 
     else { 
      // ERROR 
     } 
    } 

    ~file() { 
     if (fclose(file_)) { 
      // ERROR 
     } 
    } 

    void write(const char* str) { 
     if (EOF == fputs(str, file_)) { 
      throw runtime_error("file write failure"); 
     } 
    } 
private: 
    FILE* file_; 
}; 

그래서, fopen을 반환 NULL 경우 발생하는 오류를 처리하는 가장 좋은 방법은 무엇입니까? 왜냐하면 나는 NULL도 반환 할 수없는 생성자이기 때문입니다.

누군가가 나에게 이러한 오류를 처리하는 방법에 대한 힌트를 줄 수 있기를 바랍니다.

점멸

답변

6

에게, 실패를보고 할 수있는 생성자가 예외를 던지는 것입니다 유일한 방법 안부 감사합니다.

소멸자는 예외를 throw하지 않아야합니다 (소멸자가 스택을 풀어 낼 때 throw하면 std::terminate이 호출되어 기본적으로 프로그램이 종료됩니다).

파괴에 실패하면, 당신은

  • 삼키기 오류가 자동으로
  • 프로그램
  • 오류를 기록하고 위의 중 하나를 수행을 중단 할 수 있습니다.

RAII를 올바르게 사용하면 예외가 손상없이 코드를 통과 할 수 있습니다. 여기

예 :이 코드는 많은 버그를 가지고

#include <cerrno> 
#include <cstring> 
#include <sstream> 

file::file(const char* filename) 
{ 
    file_ = fopen(filename, "w+"); 

    if (!file_) 
    { 
     std::ostringstream os; 
     os << "Cannot open " << filename << ": " 
      << std::strerror(errno); 

     throw std::runtime_error(os.str()); 
    } 
} 

file::~file() 
{ 
    fclose(file_); 
} 

참고 :. fclose 기능이 실패 할 수 있고, 고장 또는 관련되지 않을 수있다 (예를 들어, 일부 쓰기 오류가 경우에만보고 폐쇄 POSIX 시스템에서 close 시스템 호출시 플러시). C++에서 파일 입출력을 위해 iostream을 사용하십시오. 왜냐하면 그러한 관심사에 대해 편리한 추상화를 제공하기 때문입니다.

+1

또한 소멸자는 throw하지 않아야합니다. – GManNickG

+0

"스택을 푸는 동안 소멸자가 throw하면 어떻게됩니까?" -'std :: terminate()'가 호출됩니다. 따라서 소멸자로부터 던지는 것은'abort()'를 호출하는 것만큼이나 나쁩니다. –

+0

@SteveJessop : 감사합니다. –

1

이름에도 불구하고 RAII는 반드시 자원 획득과 관련이 없습니다. std::shared_ptr은 예를 들어 new을 수행하지 않습니다. 이름은 입니다. 패턴은 실제로 정리에 관한 것입니다. File의 경우

은 물론, "올바른"대답은 std::ifstreamstd::ofstream를 사용하고, 그것으로 할 수있다. 그리고 매우 중요한 (이상 출력) : 당신이 close가 일반적인 경우에 블록을 떠나기 전에 제대로 완료되었음을 확인해야하기 때문에 RAII이 뛰어난 청소 만 사용할 수 있습니다. 닫기이 실패 할 경우 일반 규칙으로, 당신은 파일을 삭제 (및 main에서 EXIT_FAILURE을 반환하거나 오류가 분명 할 것입니다 무언가를하고 데이터가 있다고 가정에서 실행에서 추가 코드를 방지 할 쓴).따라서 을 소멸자에게 close 연기하는 것이 허용 될 수있는 유일한 시간은 이미 에 오류가 발견되어 클린업의 일부로 파일을 삭제하려고하는 경우입니다.

일반적으로 출력은 RAII보다 많은 트랜잭션 모델을 따릅니다. 나는 파일을 닫는 함수를 사용하여 OutputFile 클래스에 내 std::ofstream을 래핑하는 경향이 있으며 닫는 데 성공한 경우에만 클래스를 커밋으로 표시합니다. 소멸자가 이고 파일이 커밋되지 않은 경우 소멸자는 파일을 닫은 다음 삭제합니다. (아마도 상위 레벨은 예외를 잡아서 return EXIT_FAILURE 메인으로 변환합니다.)

또한 IO는 일반적으로 약간 예외적입니다. 일반적으로 개체를 만들 수없는 경우 어떤 이유에서든 생성자는 예외를 발생시켜야합니다. 주위에 떠 다니는 "좀비"물체를 원하지 않으므로 을 사용할 수 없습니다. 그러나 IO의 경우에는 사실을 처리 후 객체가 무효화되고 사용할 수 없게 될 수도 있습니다. 심지어 건설이 성공하면 입니다. 당신은 항상 상태를 항상 확인해야하므로 (입력시마다 읽은 다음 출력이 끝나면) 테스트 할 객체 인 에 내부 플래그를 설정하는 것이 적절합니다.

+0

"그런 다음 삭제합니다"- 물론 작업은 파일 사용 방법에 따라 달라집니다 - 로그 파일로 사용되는 경우 오류가 발생하면 삭제하는 것이별로 도움이되지 않을 수 있습니다. 그러나 파일의 새 버전을 임시 이름에 쓰는 경우 원본 파일을 원래대로 이름을 바꿀 준비를하기 위해 삭제할 때 오류가 발생할 때 정확하게 수행해야합니다. –

+0

@SteveJessop 예. 로그 파일은 특별한 경우입니다. 나는 보통 그들에 대한 오류 상태를 확인하는 것을 꺼려하지 않는다. 그들은 "최선의 노력"이다. 그러나 대부분의 다른 경우에는 불완전한 파일을 두지 않는 것이 좋습니다. 비슷하게, 에러 코드를 반환하기 위해'std :: cout'을'close' 또는'flush '하고 싶을 것이다. 하지만 일반적으로'std :: cerr'은 "최선의 노력"이어야합니다. ('cmd orig> tmp && mv tmp orig'는 Unix에서 일반적인 관용구입니다. 쓰기 실패로 인해 오류 반환 상태가 발생하지 않으면 다소 불쾌한 결과가 발생합니다.) –