소멸자에서 예외 던지기


257

대부분의 사람들은 소멸자에서 예외를 절대로 버리지 않는다고 . 그렇게하면 정의되지 않은 동작이 발생합니다. Stroustrup은 "벡터 소멸자가 모든 요소에 대해 소멸자를 명시 적으로 호출합니다. 이는 요소 소멸자가 던지면 벡터 소멸이 실패 함을 의미합니다. 소멸자에서 발생한 예외를 방지 할 수있는 좋은 방법은 없습니다. 따라서 라이브러리 요소 소멸자가 던질 경우 보증하지 않습니다 "(부록 E3.2에서) .

이 기사 는 달리 말하는 것으로 보입니다-던지는 소멸자는 다소 괜찮습니다.

그래서 내 질문은 이것입니다-소멸자에서 던지면 정의되지 않은 동작이 발생하면 소멸자 중에 발생하는 오류를 어떻게 처리합니까?

정리 작업 중 오류가 발생하면 무시 하시겠습니까? 스택에서 처리 할 수 ​​있지만 소멸자에서 바로 처리 할 수없는 오류 인 경우 소멸자에서 예외를 발생시키는 것이 합리적이지 않습니까?

분명히 이런 종류의 오류는 드물지만 가능합니다.


36
"한 번에 두 가지 예외"는 주식 답변이지만 실제 이유는 아닙니다. 실제 이유는 함수의 사후 조건을 충족시킬 수없는 경우에만 예외가 발생해야하기 때문입니다. 소멸자의 사후 조건은 객체가 더 이상 존재하지 않는다는 것입니다. 이럴 수 없습니다. 따라서 고장이 발생하기 쉬운 수명 종료 작업은 객체가 범위를 벗어나기 전에 별도의 메서드로 호출해야합니다.
spraff

29
@ spraff : 당신이 말한 것이 "RAII를 버린다"는 것을 알고 있습니까?
Kos

16
@ spraff : (객체가 범위를 벗어나기 전에 별도의 메소드를 호출해야 함) 실제로 작성한 것은 RAII를 버립니다! 이러한 객체를 사용하는 코드는 소멸자가 호출되기 전에 이러한 메소드가 호출되도록해야합니다. 마지막으로이 아이디어는 전혀 도움이되지 않습니다.
Frunsi

8
@Frunsi no.이 문제는 소멸자가 단순한 리소스 해제 이외의 작업을 시도하고 있기 때문에 발생합니다. "항상 XYZ를하고 싶다"고 말하고 싶은데, 이것이 소멸자에게 그러한 논리를 적용하는 주장이라고 생각합니다. 아니요, 게으르지 말고 xyz()소멸자를 작성 하여 비 RAII 논리를 깨끗하게 유지하십시오.
spraff

6
@Frunsi 예를 들어, 파일에 무언가를 커밋하는 것이 트랜잭션을 나타내는 클래스의 소멸자에서 반드시 수행되는 것은 아닙니다 . 커밋이 실패하면 트랜잭션과 관련된 모든 코드가 범위를 벗어 났을 때 처리하기에 너무 늦습니다. commit()메소드가 호출 되지 않으면 소멸자는 트랜잭션을 폐기해야합니다 .
Nicholas Wilson

답변:


197

소멸자에서 예외를 던지는 것은 위험합니다.
다른 예외가 이미 전파중인 경우 응용 프로그램이 종료됩니다.

#include <iostream>

class Bad
{
    public:
        // Added the noexcept(false) so the code keeps its original meaning.
        // Post C++11 destructors are by default `noexcept(true)` and
        // this will (by default) call terminate if an exception is
        // escapes the destructor.
        //
        // But this example is designed to show that terminate is called
        // if two exceptions are propagating at the same time.
        ~Bad() noexcept(false)
        {
            throw 1;
        }
};
class Bad2
{
    public:
        ~Bad2()
        {
            throw 1;
        }
};


int main(int argc, char* argv[])
{
    try
    {
        Bad   bad;
    }
    catch(...)
    {
        std::cout << "Print This\n";
    }

    try
    {
        if (argc > 3)
        {
            Bad   bad; // This destructor will throw an exception that escapes (see above)
            throw 2;   // But having two exceptions propagating at the
                       // same time causes terminate to be called.
        }
        else
        {
            Bad2  bad; // The exception in this destructor will
                       // cause terminate to be called.
        }
    }
    catch(...)
    {
        std::cout << "Never print this\n";
    }

}

이것은 기본적으로 다음과 같이 요약됩니다.

위험한 것 (즉, 예외를 던질 수있는 것)은 반드시 공개적인 방법을 통해 수행되어야합니다 (직접적인 것은 아님). 그런 다음 클래스 사용자는 공개 메소드를 사용하고 잠재적 예외를 포착하여 이러한 상황을 처리 할 수 ​​있습니다.

그런 다음 소멸자는 이러한 메소드를 호출하여 오브젝트를 종료하지만 (사용자가 명시 적으로 그렇게하지 않은 경우), 예외를 발견하고 삭제합니다 (문제를 해결하려고 시도한 후).

따라서 실제로 사용자에게 책임을 전가합니다. 사용자가 예외를 바로 잡을 수있는 위치에 있으면 적절한 함수를 수동으로 호출하고 오류를 처리합니다. 객체의 사용자가 걱정하지 않으면 (객체가 파손될 것이므로) 소멸자는 비즈니스를 처리해야합니다.

예를 들면 :

std :: fstream

close () 메소드는 잠재적으로 예외를 발생시킬 수 있습니다. 파일이 열린 경우 소멸자는 close ()를 호출하지만 예외가 소멸자에서 전파되지 않도록합니다.

따라서 파일 객체의 사용자가 파일 닫기와 관련된 문제에 대해 특별한 처리를하려면 수동으로 close ()를 호출하고 예외를 처리합니다. 반면에 그들은 신경 쓰지 않으면 소멸자는 상황을 처리하도록 남겨 둘 것입니다.

Scott Myers는 그의 저서 "Effective C ++"에서이 주제에 대한 훌륭한 기사를 가지고 있습니다.

편집하다:

"더 효과적인 C ++"
항목 11에서도 볼 수 있습니다. 예외가 소멸자를 떠나지 않도록 방지


5
"응용 프로그램을 종료 할 가능성이 없다면 오류를 삼킬 수 있습니다." -이것은 아마도 규칙이 아닌 예외 (말장난을 용서), 즉 빨리 실패해야합니다.
Erik Forbes

15
동의하지 않습니다. 프로그램을 종료하면 스택 해제가 중지됩니다. 더 이상 소멸자가 호출되지 않습니다. 열린 모든 자원은 열린 상태로 유지됩니다. 예외를 삼키는 것이 선호되는 옵션이라고 생각합니다.
Martin York

20
OS는 소유자가 소유 한 리소스를 정리할 수 있습니다. 메모리, 파일 핸들 등 복잡한 리소스의 경우 : DB 연결. 열린 ISS에 대한 업 링크 (자동으로 닫기 연결을 전송합니까)? NASA가 연결을 깨끗하게 닫기를 원할 것입니다.
마틴 요크

7
응용 프로그램이 중단되어 "빠르게 실패"하는 경우에는 우선 예외를 발생시키지 않아야합니다. 제어를 스택으로 다시 전달하여 실패하면 프로그램이 중단 될 수있는 방식으로 실패하지 않아야합니다. 둘 중 하나를 선택하지 마십시오.
Tom

2
@LokiAstari 우주선과 통신하는 데 사용하는 전송 프로토콜이 끊어진 연결을 처리 할 수 ​​없습니까? Ok ...
doug65536

54

이 소멸자는 "스택 풀기"의 일부로 호출 될 수 있기 때문에 소멸자를 제외하면 충돌이 발생할 수 있습니다. 스택 해제는 예외가 발생했을 때 발생하는 절차입니다. 이 절차에서는 "시도"이후에 예외로 처리 될 때까지 스택으로 푸시 된 모든 오브젝트가 종료되고 소멸자가 호출됩니다. 그리고이 절차 중에 한 번에 두 개의 예외를 처리 할 수 ​​없기 때문에 또 다른 예외 발생이 허용되지 않으므로 abort () 호출이 발생하고 프로그램이 중단되고 컨트롤이 OS로 돌아갑니다.


1
위 상황에서 abort ()가 어떻게 호출되었는지 자세히 설명해 주시겠습니까? 실행 제어가 여전히 C ++ 컴파일러를 사용
했음을

1
@ Krishna_Oza : 매우 간단합니다 : 오류가 발생할 때마다 오류를 발생시키는 코드는 런타임 시스템이 스택 해제 프로세스 (즉, 다른 것을 처리 throw하지만 catch아직 블록을 찾지 못함)를 처리하고 있음을 나타내는 비트를 확인합니다. 이 경우 std::terminate( abort새로운) 예외를 발생시키는 대신 (또는 스택 해제를 계속하는 대신) (not )이 호출됩니다.
Marc van Leeuwen

53

특정 사례에 대한 일반적인 조언을 맹목적으로 따르는 대신 여기 에서 차별화 해야합니다 .

다음 객체 컨테이너의 문제와 컨테이너 내부의 여러 객체에 대한 작업을 무시 합니다. (일부 개체는 컨테이너에 넣기에 적합하지 않기 때문에 부분적으로 무시할 수 있습니다.)

클래스를 두 가지 유형으로 나눌 때 전체 문제를 생각하기가 더 쉬워집니다. 클래스 dtor는 두 가지 다른 책임을 가질 수 있습니다.

  • (R) 릴리스 의미론 (일명 메모리를 해제)
  • (C) 커밋 의미 (일명 파일을 디스크로 플러시 )

우리 가이 질문을 이런 식으로 본다면, (R) 의미론은 a) 우리가 할 수있는 일이 없으며 b) 많은 자유 자원 작업이 없기 때문에 dtor로부터 예외를 일으키지 않아야한다고 주장 할 수 있다고 생각합니다. 예를 들어 오류 점검을 제공 void free(void* p); .

성공적으로 요구가 다른 종류의있는 dtor에 커밋 않는 그것의 데이터 또는 ( "범위 보호") 데이터베이스 연결을 플러시하는 파일 객체 같은 (C) 의미와 객체 : 우리는 할 수 있습니다 오류에 대해 뭔가를 (에 우리는 실제로 아무 일도 일어나지 않는 것처럼 계속해서는 안됩니다.

RAII 경로를 따르고 d' tors에 (C) 시맨틱이있는 객체를 허용하면 그러한 d' tor가 던질 수있는 이상한 경우도 허용해야한다고 생각합니다. 그런 다음 이러한 오브젝트를 컨테이너에 넣지 말아야 terminate()하며, 또 다른 예외가 활성화되어있는 동안 commit-dtor가 발생하는 경우 에도 프로그램이 계속 수행 할 수 있습니다 .


오류 처리 (/ 롤백 의미를 커밋) 예외와 관련하여, 하나 좋은 이야기가 안드레이 알렉산드 레스 쿠는 : C에서 오류 처리 ++ / 선언적 제어 흐름 (에서 열린 2014 NDC )

세부 정보 창에서, 그는 어떻게 어리 석음 라이브러리 구현 설명 UncaughtExceptionCounter에 대한 그들의ScopeGuard 툴링을 .

( 다른 사람들이 도 비슷한 아이디어를 가지고 .)

대화는 의사에게 던지는 것에 초점을 맞추지 않지만, 던질 때문제를 없애기 위해 오늘날 사용할 수있는 도구를 보여줍니다 에게서 .

에서 미래 ,가 이 들어, 표준 기능이 될 참조 N3614를 , 그리고 그것에 대해 토론 .

Upd '17 : 이에 대한 C ++ 17 표준 기능이 std::uncaught_exceptions잘못되었습니다. 나는 cppref 기사를 빨리 인용 할 것이다 :

노트

int-returning uncaught_exceptions이 사용되는 예 는 ... ... 먼저 가드 객체를 만들고 생성자에서 포착되지 않은 예외 수를 기록합니다. 출력은 foo ()가 발생하지 않는 한 가드 객체의 소멸자에 의해 수행됩니다 ( 이 경우 소멸자에서 포착되지 않은 예외의 수가 생성자가 관찰 한 것보다 큼 )


6
매우 동의합니다. 그리고 하나 이상의 시맨틱 (Ro) 롤백 시맨틱을 추가합니다. 스코프 가드에서 일반적으로 사용됩니다. ON_SCOPE_EXIT 매크로를 정의한 프로젝트의 경우와 같습니다. 롤백 시맨틱의 경우 여기서 의미있는 것이 발생할 수 있습니다. 따라서 실패를 무시해서는 안됩니다.
Weipeng L

소멸자에서 의미를 커밋 한 유일한 이유는 C ++이 지원하지 않기 때문 finally입니다.
user541686

@Mehrdad은 : finally 입니다 dtor. 무엇이든 항상 호출됩니다. 마지막 구문 근사에 대해서는 다양한 scope_guard 구현을 참조하십시오. 요즘에는 dtor가 던질 수 있는지 여부를 감지하기 위해 기계가 표준 (심지어 C ++ 14입니까?)으로되어있어 완전히 안전하게 만들 수 있습니다.
Martin Ba

1
@MartinBa : 나는 당신이 내 의견의 요점을 놓쳤다 고 생각합니다. (R)과 (C)가 다르다는 당신의 생각 에 동의 했기 때문에 놀랍습니다 . 나는 dtor가 본질적으로 (R) finally의 도구이고 본질적으로 (C)의 도구 라고 말하려고했습니다 . 이유를 모를 경우 : finally블록 에서 서로 예외를 던지는 것이 합법적 인 이유와 소멸자에게 왜 그렇지 않은지 를 고려하십시오. (어떤 의미에서, 그것은 데이터 대 제어적인 것입니다. 소멸자는 데이터를 방출하기위한 것이고, 제어 를 방출하기위한 것입니다 finally. 그것들은 다릅니다. C ++이 그것들을 함께 묶는 것은 불행합니다.)
user541686

1
@Mehrdad : 너무 오래 걸리고 있습니다. 원하는 경우 programmers.stackexchange.com/questions/304067/…에서 인수를 작성할 수 있습니다 . 감사.
Martin Ba

21

소멸자로부터 던지는 것에 대해 스스로에게 묻는 실제 질문은 "발신자가 이것으로 무엇을 할 수 있습니까?"입니다. 실제로 소멸자로부터 던질 때 발생하는 위험을 상쇄 할 수있는 예외와 관련하여 유용한 것이 있습니까?

Foo물체를 파괴 하고 Foo소멸자가 예외를 던지면 합리적으로 무엇을 할 수 있습니까? 로그하거나 무시할 수 있습니다. 그게 다야. Foo개체가 이미 없어서 "수정"할 수 없습니다 . 가장 좋은 경우, 예외를 기록하고 아무 일도없는 것처럼 계속합니다 (또는 프로그램을 종료하십시오). 소멸자에서 던져서 정의되지 않은 동작을 유발할 가치가 있습니까?


11
방금 알아 차린 ... dtor에서 던지는 것은 정의되지 않은 행동 이 아닙니다 . 물론, 그것은 terminate ()를 호출 할 수 있지만, 그것은 매우 잘 지정된 행동입니다.
Martin Ba

4
std::ofstream의 소멸자가 파일을 플러시 한 다음 닫습니다. 플러시하는 동안 디스크 가득 참 오류가 발생할 수 있으며, 이는 다음과 같은 유용한 기능을 수행 할 수 있습니다. 디스크에 여유 공간이 없다는 오류 대화 상자를 표시하십시오.
Andy

13

위험하지만 가독성 / 코드 이해도 관점에서 의미가 없습니다.

당신이 물어봐야 할 것은이 상황입니다

int foo()
{
   Object o;
   // As foo exits, o's destructor is called
}

예외는 무엇입니까? foo를 호출해야합니까? 아니면 foo가 처리해야합니까? foo 호출자가 foo 내부의 일부 객체를 신경 써야하는 이유는 무엇입니까? 언어가 이것을 이해하도록 정의하는 방법이있을 수 있지만 읽을 수없고 이해하기 어려울 것입니다.

더 중요한 것은 Object의 메모리는 어디에 있습니까? 객체가 소유 한 메모리는 어디에 있습니까? 소멸자가 실패했기 때문에 여전히 할당되어 있습니까? 객체가 스택 공간 에 있었으므로 분명히 상관없이 사라졌습니다.

그런 다음이 경우를 고려하십시오

class Object
{ 
   Object2 obj2;
   Object3* obj3;
   virtual ~Object()
   {
       // What should happen when this fails? How would I actually destroy this?
       delete obj3;

       // obj 2 fails to destruct when it goes out of scope, now what!?!?
       // should the exception propogate? 
   } 
};

obj3 삭제가 실패하면 실제로 실패하지 않는 방법으로 어떻게 삭제합니까? 내 기억이 엉망이야!

이제 첫 번째 코드 스 니펫에서 Object3가 힙에 있고 스택에 있기 때문에 자동으로 사라지는 것을 고려하십시오. Object3에 대한 포인터가 사라 졌으므로 일종의 SOL입니다. 메모리 누수가 있습니다.

이제 일을하는 안전한 방법은 다음과 같습니다.

class Socket
{
    virtual ~Socket()
    {
      try 
      {
           Close();
      }
      catch (...) 
      {
          // Why did close fail? make sure it *really* does close here
      }
    } 

};

FAQ 도 참조하십시오


이 대답을 다시 부활시켜 re : 첫 번째 예 인 about int foo()-function-try-block을 사용하여 소멸자를 잡는 것을 포함하여 전체 함수 foo를 try-catch 블록으로 래핑 할 수 있습니다. 여전히 선호되는 접근 방식은 아니지만 문제입니다.
tyree731

13

C ++ 용 ISO 초안에서 (ISO / IEC JTC 1 / SC 22 N 4411)

따라서 소멸자는 일반적으로 예외를 잡아서 소멸자로부터 전파되지 않도록해야합니다.

3 try 블록에서 throw 식까지의 경로에 구성된 자동 객체에 대해 소멸자를 호출하는 프로세스를 "스택 풀기"라고합니다. [참고 : 스택 해제 중 호출 된 소멸자가 예외와 함께 종료되면 std :: terminate가 (15.5.1)이라고합니다. 따라서 소멸자는 일반적으로 예외를 잡아서 소멸자로부터 전파되지 않도록해야합니다. — 끝 참고]


1
질문에 대답하지 않았습니다-OP는 이미 이것을 알고 있습니다.
Arafangion

2
@ Arafangion 나는 받아 들인 대답이 정확히 똑같은 점을 알았 기 때문에 이것이 (std :: terminate라고 함) 알고 있다고 의심합니다.
lothar 2016 년

@Arafangion 일부 답변에서와 같이 일부 사람들은 abort ()가 호출되었다고 언급했습니다. 또는 std :: terminate가 차례로 abort () 함수를 호출합니다.
Krishna Oza

7

소멸자는 다른 소멸자 내에서 실행 중일 수 있습니다. 즉시 호출자가 잡지 않은 예외를 처리하면 여러 오브젝트가 일관성이없는 상태로 남아있어 더 많은 문제점이 발생하여 정리 조작에서 오류를 무시할 수 있습니다.


7

나는 소멸자에 던지는 "스코프 가드"패턴이 많은 상황, 특히 단위 테스트에 유용하다고 생각하는 그룹에 속합니다. 그러나 C ++ 11에서는 std::terminate소멸자가 내재적으로 주석 처리되어 있기 때문에 소멸자 를 던지면 호출이 발생합니다 noexcept.

Andrzej Krzemieński는 다음을 던지는 소멸자 주제에 대한 훌륭한 게시물을 가지고 있습니다.

그는 C ++ 11 noexcept에 소멸자 의 기본값을 재정의하는 메커니즘이 있다고 지적합니다 .

C ++ 11에서 소멸자는 암시 적으로로 지정됩니다 noexcept. 사양을 추가하지 않고 다음과 같이 소멸자를 정의하더라도 :

  class MyType {
        public: ~MyType() { throw Exception(); }            // ...
  };

컴파일러는 여전히 보이지 않게 noexcept소멸자 에 사양 을 추가 합니다. 그리고 이것은 std::terminate이중 예외 상황이 없더라도 소멸자가 예외를 던지는 순간에 호출됩니다. 소멸자가 던질 수 있도록 결정했다면이를 명시 적으로 지정해야합니다. 세 가지 옵션이 있습니다.

  • 소멸자를 명시 적으로 noexcept(false),
  • 이미 소멸자를로 지정하는 다른 클래스에서 클래스를 상속하십시오 noexcept(false).
  • 이미 소멸자를로 지정하는 비 정적 데이터 멤버를 클래스에 넣습니다 noexcept(false).

마지막으로 소멸자를 처리하기로 결정한 경우 항상 이중 예외의 위험에 대해 알고 있어야합니다 (예외로 인해 스택이 풀리는 동안 발생). 이로 인해 전화 std::terminate가 걸리고 원하는 것은 거의 없습니다. 이 동작을 피하려면을 사용하여 새 예외를 던지기 전에 이미 예외가 있는지 확인하면됩니다 std::uncaught_exception().


6

다른 사람들은 왜 소멸자를 던지는 것이 끔찍한 지 설명했습니다. 어떻게 할 수 있습니까? 실패 할 수있는 작업을 수행하는 경우 정리를 수행하고 임의의 예외가 발생할 수있는 별도의 공용 메서드를 만듭니다. 대부분의 경우 사용자는이를 무시합니다. 정리 성공 / 실패를 모니터링하려는 경우 명시 적 정리 루틴을 호출하면됩니다.

예를 들면 다음과 같습니다.

class TempFile {
public:
    TempFile(); // throws if the file couldn't be created
    ~TempFile() throw(); // does nothing if close() was already called; never throws
    void close(); // throws if the file couldn't be deleted (e.g. file is open by another process)
    // the rest of the class omitted...
};

나는 해결책을 찾고 있지만 그들이 일어난 일과 이유를 설명하려고합니다. 닫기 함수가 소멸자 내부에서 호출되는지 명확히하고 싶습니까?
Jason Liu

5

훌륭하고 포괄적이며 정확한 주요 답변 외에도 "소멸자에서 예외를 던지는 것은 그렇게 나쁘지 않습니다"라는 기사에 대해 언급하고 싶습니다.

이 기사는 "예외를 던지는 대안은 무엇입니까?"라는 문구를 사용하고 각 대안에 대한 몇 가지 문제점을 나열합니다. 그렇게하면 문제가없는 대안을 찾을 수 없으므로 예외를 계속 발생시켜야한다는 결론을 내립니다.

문제는 대안들과 함께 나열된 문제들 중 어느 것도 예외적 인 행동만큼이나 나쁘지 않다는 것입니다. 그것은 기억하십시오, "프로그램의 정의되지 않은 행동"입니다. 저자의 반대 의견 중 일부는 "미학적으로 못 생겼다"와 "나쁜 스타일 장려하기"를 포함합니다. 이제 어느 쪽이 좋을까요? 스타일이 나쁜 프로그램이나 정의되지 않은 행동을 보이는 프로그램?


1
정의되지 않은 행동이 아니라 즉각적인 종료.
Marc van Leeuwen

표준은 '정의되지 않은 행동'이라고 말합니다. 그 행동은 자주 종료되지만 항상 그런 것은 아닙니다.
DJClayworth

아니오, 예외 처리-> 특수 함수 (표준 사본에서는 15.5.1이지만 번호가 구식 일 수 있음)에서 [except.terminate]를 읽으십시오.
Marc van Leeuwen 2016

2

Q : 내 질문은 이것입니다-소멸자에서 던질 때 정의되지 않은 동작이 발생하면 소멸자 중에 발생하는 오류를 어떻게 처리합니까?

A : 몇 가지 옵션이 있습니다.

  1. 다른 곳에서 일어나고있는 일에 관계없이 예외가 소멸자에서 흘러 나오게하십시오. 그리고 그렇게 할 때 std :: terminate가 뒤따를 수 있음을 인식하십시오.

  2. 소멸자에게 예외가 흘러 가지 않도록하십시오. 가능한 경우 큰 빨간색 나쁜 텍스트를 로그에 쓸 수 있습니다.

  3. 내 fave : std::uncaught_exceptionfalse를 반환하면 예외가 발생합니다. true를 반환하면 로깅 방식으로 돌아갑니다.

그러나 d' tors에 던지는 것이 좋은가요?

나는 위의 대부분이 소멸자에서 가능한 한 피하는 것이 가장 좋습니다. 그러나 때때로 당신은 그것이 일어날 수 있다는 것을 받아들이고 잘 처리하는 것이 가장 좋습니다. 위의 3을 선택하겠습니다.

실제로 소멸자에게 던질 좋은 아이디어 가있는 몇 가지 이상한 경우가 있습니다. "확인해야합니다"오류 코드와 같습니다. 이것은 함수에서 반환되는 값 형식입니다. 호출자가 포함 된 오류 코드를 읽거나 확인하면 반환 된 값이 자동으로 소멸됩니다. 그러나 반환 값이 범위를 벗어날 때까지 반환 된 오류 코드를 읽지 않으면 소멸자에서 예외가 발생 합니다.


4
귀하의 페이브 내가 최근에 시도 뭔가이며, 그것은 당신이해야 판명 되지 를 않습니다. gotw.ca/gotw/047.htm
GManNickG

1

나는 현재 클래스가 소멸자로부터 예외를 적극적으로 던져서는 안되고 대신 실패 할 수있는 작업을 수행하기 위해 공개 "닫기"방법을 제공해야한다는 정책을 (많은 사람들이 말하고 있음) 따르고 있습니다 ...

... 그러나 벡터와 같은 컨테이너 유형 클래스의 소멸자는 포함 된 클래스에서 발생하는 예외를 마스킹해서는 안된다고 생각합니다. 이 경우 실제로 재귀 적으로 호출되는 "free / close"메서드를 사용합니다. 예, 재귀 적으로 말했습니다. 이 광기에 대한 방법이 있습니다. 예외 전파는 스택에 의존합니다. 단일 예외가 발생하면 나머지 소멸자가 여전히 실행되고 루틴이 리턴되면 보류중인 예외가 전파됩니다. 여러 예외가 발생하면 (컴파일러에 따라) 첫 번째 예외가 전파되거나 프로그램이 종료됩니다. 재귀로 인해 스택에 오버플로가 발생하는 예외가 너무 많이 발생하면 심각한 문제가 발생하여 누군가가 그것에 대해 알게 될 것입니다. 몸소,

요점은 컨테이너가 중립을 유지하고 소멸자로부터 예외를 던지는 것과 관련하여 컨테이너가 동작하는지 오작동 하는지를 결정하는 것은 포함 된 클래스에 달려 있다는 것입니다.


1

예외를 던지는 것이 객체 생성이 성공했음을 나타내는 유용한 방법 일 수있는 생성자와는 달리, 소멸자에서 예외를 발생시키지 않아야합니다.

스택 해제 프로세스 중에 소멸자에서 예외가 발생하면 문제가 발생합니다. 이 경우 컴파일러는 스택 해제 프로세스를 계속할지 아니면 새 예외를 처리할지 여부를 모르는 상황에 처하게됩니다. 결과적으로 프로그램이 즉시 종료됩니다.

결과적으로 최선의 조치는 소멸자에서 예외를 사용하지 않는 것입니다. 대신 로그 파일에 메시지를 작성하십시오.


1
로그 파일에 메시지를 쓰면 예외가 발생할 수 있습니다.
Konard

1

Martin Ba (위)는 RELEASE와 COMMIT 논리에 대해 다르게 설계했습니다.

출시 :

오류를 먹어야합니다. 메모리를 비우거나 연결을 끊는 등의 작업을 수행하고 있습니다. 시스템의 어느 누구도 이러한 정보를 다시 볼 수 없으며 OS에 리소스를 넘겨 줄 수 없습니다. 여기에 실제 오류 처리가 필요한 것 같으면 객체 모델의 디자인 결함으로 인한 것일 수 있습니다.

커밋 :

여기서 std :: lock_guard와 같은 것들이 뮤텍스를 제공하는 것과 같은 종류의 RAII 래퍼 객체를 원합니다. 그것들을 사용하면 커밋 로직을 dtor AT ALL에 넣지 않습니다. 전용 API가 있고, RAIR가 해당 API를 RAIR 커밋하고 오류를 처리하는 래퍼 객체를 제공합니다. 소멸자에서 예외를 잘 잡을 수 있다는 것을 기억하십시오. 그들에게 치명적인 발행. 또한 다른 래퍼 (예 : std :: unique_lock vs. std :: lock_guard)를 작성하여 정책 및 다른 오류 처리를 구현할 수 있으며, 커밋 논리를 호출하는 것을 잊지 않아야합니다. 그것을 첫 번째 장소에 dtor에 넣는 것에 대한 적절한 정당화.


1

그래서 내 질문은 이것입니다-소멸자에서 던지면 정의되지 않은 동작이 발생하면 소멸자 중에 발생하는 오류를 어떻게 처리합니까?

주요 문제는 이것입니다 : 당신은 실패 할 수 없습니다 . 결국 실패하지 못한다는 것은 무엇을 의미합니까? 트랜잭션을 데이터베이스에 커밋하는 데 실패하고 실패 (롤백 실패)하면 데이터의 무결성은 어떻게됩니까?

소멸자는 일반적인 경로와 예외적 인 (실패한) 경로 모두에 대해 호출되므로 스스로 실패 할 수 없거나 "실패하지 못합니다".

이것은 개념적으로 어려운 문제이지만 종종 해결책은 실패가 실패하지 않도록하는 방법을 찾는 것입니다. 예를 들어 데이터베이스는 외부 데이터 구조 또는 파일을 커밋하기 전에 변경 사항을 쓸 수 있습니다. 트랜잭션이 실패하면 파일 / 데이터 구조를 버릴 수 있습니다. 그러면 외부 구조 / 파일에서 변경 사항을 커밋해도 실패하지 않는 원자 트랜잭션을 보장 할 수 있습니다.

실용적 해결책은 실패에 실패하는 것을 불가능하게 만드는 것이 거의 불가능할 수 있기 때문에 실패에 실패 할 가능성을 천문학적으로 불가능하게 만드는 것입니다.

나에게 가장 적합한 해결책은 정리 논리가 실패하지 않는 방식으로 비 정리 논리를 작성하는 것입니다. 예를 들어, 기존의 데이터 구조를 정리하기 위해 새로운 데이터 구조를 만들고 싶은 경우, 더 이상 소멸자 내부에서 생성 할 필요가 없도록 해당 보조 구조를 미리 만들 수 있습니다.

이것은 분명히 말한 것보다 훨씬 쉽지만, 그것에 관해 내가 보는 유일한 방법입니다. 때로는 예외적 인 경로에서 멀리 떨어져있는 일반적인 실행 경로에 대해 별도의 소멸자 논리를 작성하는 기능이 있어야한다고 생각합니다. 파괴자가 두 가지를 모두 처리하여 책임을 두 배로 느끼는 것처럼 느껴지기 때문에 (예 : 명시 적 해고가 필요한 스코프 가드) 예외적 인 경로와 예외가 아닌 경로를 구별 할 수 있다면이를 필요로하지 않을 것입니다).

여전히 궁극적 인 문제는 우리가 실패 할 수 없다는 것입니다. 모든 경우에 완벽하게 해결하는 것은 어려운 개념 설계 문제입니다. 복잡한 물체가 서로 상호 작용하는 복잡한 제어 구조로 너무 복잡하게 싸이 지 않고 약간 더 큰 방식으로 설계를 모델링하는 것이 더 쉽습니다 (예 : 전체 입자를 파괴하는 소멸자가있는 입자 시스템) 파티클 당 별도의 소멸자가 아닌 시스템). 이런 종류의 거친 수준에서 디자인을 모델링 할 때 다루기 어려운 소멸자가 적고 소멸자가 실패하지 않도록 메모리 / 프로세싱 오버 헤드가 필요한 경우가 종종 있습니다.

그리고 자연스럽게 가장 쉬운 해결책 중 하나는 소멸자를 덜 자주 사용하는 것입니다. 위의 입자 예에서, 아마도 입자를 파괴 / 제거 할 때 어떤 이유로 든 실패 할 수있는 몇 가지 작업을 수행해야합니다. 이 경우 예외 경로에서 실행될 수있는 입자의 dtor를 통해 이러한 논리를 호출하는 대신 입자를 제거 할 때 입자 시스템에서 모두 수행 할 수 있습니다 . 예외가 아닌 경로에서 항상 입자 제거가 수행 될 수 있습니다. 시스템이 파괴되면 모든 파티클을 제거하고 실패 할 수있는 개별 파티클 제거 로직을 방해하지 않을 수 있지만 실패 할 수있는 로직은 하나 이상의 파티클을 제거 할 때 파티클 시스템의 정상적인 실행 중에 만 실행됩니다.

사소한 소멸자가 아닌 많은 작은 물체를 다루지 않으면 자라는 솔루션과 같은 솔루션이 종종 있습니다. 예외 안전이 거의 불가능 해 보이는 혼란에 빠질 수있는 곳은 모두 사소한 dtor가있는 많은 작은 물건에 얽힌 경우입니다.

nothrow / noexcept가 실제로 그것을 지정하는 것 (기본 클래스의 noexcept 사양을 상속 해야하는 가상 함수 포함)이 던질 수있는 것을 호출하려고 시도하면 실제로 컴파일러 오류로 변환되면 많은 도움이 될 것입니다. 이렇게하면 실수로 던질 수있는 소멸자를 실제로 작성하면 컴파일 타임 에이 모든 것을 잡을 수 있습니다.


1
파괴는 지금 실패인가?
curiousguy

나는 그가 실패를 제거하기 위해 소멸자가 호출되었다는 것을 의미한다고 생각합니다. 따라서 활성 예외 중에 소멸자가 호출되면 이전 실패에서 정리에 실패합니다.
user2445507

0

알람 이벤트를 설정하십시오. 일반적으로 경보 이벤트는 물체를 청소하는 동안 고장을 알리는 더 나은 형태입니다.

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