2010-03-24 3 views
2

Java 사용 경험이 있고 최근에 C++ 코딩을하고 있습니다. 내 질문은 클래스 A와 B 클래스 C 인스턴스를 A의 멤버 변수 중 두 가지로 인스턴스화해야한다는 것입니다.C++ construtor에서 bad_alloc 예외를 관리합니다.

A 클래스의 생성자에서 클래스 B와 클래스 C의 할당을 C는 절대 실패하지 않으며 A의 소멸자에서 잘못된 할당 예외를 처리합니까?

내가 가정하지 않는다면, 클래스 B와 클래스 C의 bad_alloc을 잡기 위해 try catch 블록을 추가한다는 것을 의미합니다. 할당 예외가 발생하면 A의 생성자에서 정리해야합니까?

권장되는 방법은 무엇입니까? "new"가 잘못된 할당을 생성하면 포인터는 어떤 값을 전달합니까?

답변

6

A를 생성하는 동안 예외가 발생하면 소멸자가 이 아닌이 호출됩니다.

분명히 해결책은 당신이하고있는 것에 달려 있지만, 이상적으로는 이 없으므로 정리할 필요가 없습니다. RAII을 활용해야하며, 반원들이 직접 청소해야합니다.

즉, 원시 포인터를 사용하지 마십시오. 그들을 감싸서 래퍼가 처리하도록하십시오. 놀람! C++ 프로그래머는 당신과 마찬가지로 메모리 관리를 싫어합니다. 우리는 그것을 감싸고 잊어 버리고 싶어합니다.

당신이 진정으로하지만, 나는 이것이 일반적인 생각해야하는 경우 :

struct foo 
{ 
    int* i; 
    some_member_that_could_throw crap; 

    foo() // do *not* new i! if the second member throws, memory is leaked. 
    {  // rather: 

     // okay we made it, the other member must have initialized 
     i = new int; 
    } 
}; 

이 포인터에 관하여,이 값의 변경되지 않습니다. new이 어떤 이유로 든 예외를 throw하면 스택이 해제됩니다. 표현의 나머지 부분은 버려졌습니다.


다음은 예외 및 개체 생성의 작동 방식입니다. 재귀 적 프로세스입니다. 각 멤버 또는 기본 클래스가이 목록을 차례로 따르기 때문입니다. 기본 유형에는 생성자가 없습니다. 이것은 재귀에 대한 기본 사례입니다.

  1. 먼저 각 기본 클래스를 구성하십시오. 이 목록을 차례로 실행하십시오.
  2. 클래스 구성원을 하나씩 초기화하십시오.
  3. 생성자 본문을 실행하십시오.
  4. 완벽하게 구성된 개체로 마무리하십시오.

분명히, 항목 1이 실패하면 우리 회원 중 누구도 초기화되지 않았으므로 우리가해야 할 정리가 없습니다. 우린 잘 했어.

두 가지가 다릅니다. 그 중 하나가 실패 할 경우 초기화 된 멤버 은 지금까지이 파괴되고 생성자가 진행을 멈추고 예외는 메리 방식으로 진행됩니다. 이것은 회원들이 스스로를 정리할 때 걱정할 것이없는 이유입니다. 초기화되지 않은 함수는 아무 것도하지 않으며, 초기화 된 함수는 소멸자가 실행되어 정리가 수행됩니다.

3 명이 더 그렇습니다.개체가 완전히 초기화되었으므로 이제는 소멸자가 모두 실행됩니다. 다시 말하지만 걱정하지 않아도됩니다.

try 
{ 
    // some code 
} 
catch (..) // catch whatever 
{ 
    delete myrawPointer; // stop the leak! 
    throw; // and let the exception continue 
} 

그것은 RAII없이 예외 안전한 코드를 작성하기가 훨씬 더 지저분가 : 당신 주위에 거짓말을 원시 포인터가있는 경우 그러나,이 try/catch 블록을위한 시간이다.

+0

무엇이 RAII입니까? 당신은 정교 할 수 있습니까? –

+0

@ 지미 장 : http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization –

+0

그래서 생성자에서 try/catch를 수행하지 말고 A의 생성자에서 B 및 C에 대한 소멸자를 호출하지 않는 것이 좋습니다 예외? –

1

간단한 부분 먼저 : 새로운 오류가 발생하여 std :: bad_alloc을 throw하면 아무 것도 포인터에 할당되지 않습니다. 새로운 (nothrow)를 사용할 수 있습니다.이 경우 throw하는 대신 null을 반환하지만 이는 드문 경우입니다.

당신이 뭔가를 할 수 없다면 bad_alloc을 잡아서는 안된다고 생각합니다. 클래스 A가 B와 C를 할당하지 못하면 어떻게 복구 할 수 있습니까? 거의 드문 경우이지만 가능하다면 거의 모든 상황에서 A의 구성이 전체적으로 실패하도록 예외를 포착하지 않는 것이 좋습니다.

이 발생할 수있는 모든 bad_alloc을 잡으려고하면 코드가 try/catch 문과 조건문을 빠르게 혼란에 빠뜨릴 것입니다 (매번 시도 할 때마다 A의 B 및 C 회원은 성공적으로 구축되었는지 확인해야합니다.) B와 C가 A가되기 위해 적절하게 구성되도록 요구하는 것이 훨씬 쉬운 IMO.

저는 B와 C가 상당히 작다고 가정하고 있습니다. 따라서 제작이 실패하는 경우는 드물 것입니다. 그럴 가능성이 더 큰 경우 (예 : 300MB 버퍼를 할당하려는 경우) 다르게 접근 할 수 있습니다. 그러나 그렇지 않은 것으로 추측합니다.

+0

B와 C의 소멸자를 호출 한 다음 bad_alloc을 throw하여 A가 실패하더라도이 접근법에 대해 어떻게 생각합니까? –

+0

음, 일반적으로 소멸자를 직접 호출하지 않을 것입니다. 나는 B가 OK로 할당되고 C가 실패한 경우에 대해 걱정할 수 있다고 생각합니다. bad_alloc을 잡아 내고 B를 삭제하고 다시 던지면 해결할 수 있지만 스마트 포인터를 사용하는 것이 더 쉬울 것입니다. 아무것도 잡으려고. – Peter

4

@Gman의 대답은 매우 완전합니다. 특정 질문에 대해 좀 더 구체적으로하기 위해, 어떤 시점에서든 예외가 발생하면 의 모든 객체는 소멸자를 호출하게됩니다. 부분적으로 생성 된 객체에는 소멸자가 호출되지 않습니다. 이제 긴 이야기 ...

그 의미는 다음과 같습니다. 모든 개별 리소스 (자체 번들이 아닌)가 자체 RAII 메커니즘으로 관리되는 경우 괜찮습니다. 첫 번째 개체가 만들어지고 리소스가 RAII 메커니즘에 의해 처리되고 두 번째 개체가 만들어집니다. 어떤 예외가 발생하면 어떤 시점에서 획득 된 모든 자원은 이미 구축 된 RAII 홀더에서 관리되어 해제됩니다.

struct willthrow { 
    willthrow() { throw std::exception(); } 
}; 
class bad { 
public: 
    bad() : a(new int(0)), b() {} 
    ~bad() { delete a; } 
private: 
    int * a; 
    willthrow b; 
}; 
class good { 
public: 
    good() : a(new int(0)), b() {} 
private: 
    std::auto_ptr<int> a; 
    willthrow b; 
} 

bad의 경우 두 번째 요소가 생성 될 때 예외가 발생합니다. 그 시점에서 a은 리소스 (할당 된 메모리)를 보유하고 있지만 직접하고 있습니다. bad의 소멸자는 완전히 구축되지 않았으므로 호출되지 않습니다. 따라서 코드에서 a이 해제되어 ~bad()에서 해제 될 수 있다고해도 절대로 호출되지 않으며 메모리 누수가 있습니다.

good 케이스에서는 메모리가 할당되어 인 a에 전달됩니다. a 서브 오브젝트는 b 초기화가 시작되기 전에 완전히 구성됩니다. b 생성자가 throw 될 때 컴파일러는 ~a을 호출합니다 (하위 개체가 완전히 구성되어 있음을 기억하십시오). 그러면 할당 된 메모리가 해제됩니다. 또한 good 클래스에는 소멸자가 없습니다. 모든 리소스는 이미 하위 객체에 의해 관리되므로 수동으로 그러한 작업을 수행 할 필요가 없습니다.RAII 관용구를 사용

1

는 두통에서 당신 (및 코드)을 방지 :

class A {}; 
class B { 
public: 
    B() {throw std::exception();} 
}; 

class C { 
public: 
    C() { 
    a.reset(new A()); 
    b.reset(new B()); //failes with std::exception 
    //after b ctor throws exception, all destructor for fully contructed objects would be called, 
    //i.e. a destructor would be called automaticly 
    } 
    ~C() { 
    //destructor is empty, because RAII all the stuff for us 
    } 

private: 
    std::auto_ptr<A> a; 
    std::auto_ptr<B> b; 
}; 

그리고 난 당신이 허브가 정확히 같은 문제를 설명 허브 셔터에 의해 greate 기사 "Constructor Exceptions in C++, C#, and Java"를 읽어한다고 생각합니다.

관련 문제