2013-04-05 2 views
24

나는 항상 std::string 또는 메모리를 할당하는 다른 클래스를 던져서는 안된다는 것을 읽습니다. here 또는 더 중요한 것은 포인트 3에 here입니다. - std::string 개체을 포함시키지 마십시오.(not) 예외에서 std :: string 사용

이제는 내 프로젝트에 boost::exception을 삽입하려고하고 있는데 무엇이 표시됩니까 : lots of strings.

부스트를 권장하지 않는 이유는 무엇입니까?

그리고 config-file에서와 같이 하드 코드 할 수없는 매개 변수가있는 경우 std::string을 사용하지 않고 예외에 넣을 수있는 방법은 무엇입니까?

또는 가이드 라인 이 가능 지침대로 거의 std::string를 사용하지 않는 std::string를 사용하지 않는입니까? 나는 조금 혼란 스럽다 ...

나는 약간 연구를했다. 내가 틀렸다면 나를 바로 잡으세요. 내가 제대로 이해하면


, 그것은 던져 동안 할당 에 대해 전부 및 할당 된 메모리에 무슨 일이 일어나고 있는지. 따라서 메모리를 생성자에 할당하면 메모리가 손실되고 예외의 소멸자에서 해제 할 수 없으므로 메모리 누수가 발생합니다. 던지기 전에 이것을 할당하는 것은 괜찮습니다. 따라서 예외는 깨끗합니다. 이 결과는

struct xexception { 
    int *ttt[10]; 
    xexception() { 
    ttt[0] = new int[0xfffffffL]; 
    ttt[1] = new int[0xfffffffL]; 
    ttt[2] = new int[0xfffffffL]; 
    ttt[3] = new int[0xfffffffL]; 
    ttt[4] = new int[0xfffffffL]; 
    ttt[5] = new int[0xfffffffL]; 
    ttt[6] = new int[0xfffffffL]; 
    ttt[7] = new int[0xfffffffL]; 
    ttt[8] = new int[0xfffffffL]; 
    ttt[9] = new int[0xfffffffL]; 
    } 

    ~xexception() throw() { 
    //never happen 
    delete[] ttt[0]; 
    delete[] ttt[1]; 
    delete[] ttt[2]; 
    delete[] ttt[3]; 
    delete[] ttt[4]; 
    delete[] ttt[5]; 
    delete[] ttt[6]; 
    delete[] ttt[7]; 
    delete[] ttt[8]; 
    delete[] ttt[9]; 
    } 
}; 

int main(int argc, const char *argv[]) { 
    try { 
    throw(xexception()); 
    } 
    catch (const xexception &e) { 
    std::cerr << "\nttt " << e.ttt[0][0] << std::endl; 
    } 
    catch (std::bad_alloc) { 
    std::cerr << "bad alloc" << std::endl; 
    } 

    return 0; 
} 

, 나는 bad_alloc 뿐이다 거대한 메모리 누수를 얻을 :

나는이 시도.

이전에 할당을 수행하면 도 예외가 생성되기 전에 bad_alloc을 발생시킵니다.


예외 개념 내 예외입니다 : 관심

? 내 프로그램에 bad_alloc이 있다면, memory_leak 나 다른 것 때문에 (나는 마이크로 컨트롤러가 아닌 PC의 프로그램에 대해 이야기하고있다.) 다른 문제가있다. 어쩌면 bad_alloc이 발생했는지 알 수 있습니다. 그러나 어디에서? 함수 중 하나 (어쩌면 1000) 또는 std::string (내가 문자열이라는 것을 알지만 ... 문자열의 메모리를 조작 할 가능성이 없다 ... 또는 그 소멸되는) 동안의 alloc에서.

try { 
    // where is the error??? 
    int *x = new int[100]; // here? 
    .... 
    int *y = new int[100]; // or here? 
    .... 
    int *z = new int[100]; 
    .... 
    int *w = new int[100]; 
    .... 
    int *t = new int[100]; 
    .... 
    int *f = new int[100]; 

    .... 

    std::string str("asdfasdfasdfasdfasdfasdfasdf"); // maybe here 
} 
catch (the error) { 
    .... 
} 

그리고? 어디에서 일어나는지 알아 내려고할까요? 따라서 나는 예외가 아닌 valgrind을 사용할 것입니다.

void foo() { 
    int *i = new int[1]; 
    foo(); 
} 

try { 
    foo(); 
} 
chatch(bad_boy) { 
    go_exception_handler_go(parameters); // oh, shit happens: also an stack_overflow may happend, cause stack is also full 
} 

아니면 errormessage를 조작하고 로그하여 다음 bad_alloc을 던질 것입니다.

저를 오해하지 마십시오.나는 boost :: exception을 보았으므로 (예외를 기다릴 때까지) 예외 클래스를 다시 작성했지만, 모든 모래알을 집어들 필요가 없다고 생각한다.

+3

'exception'의 * members * 인 std :: string은 보이지 않습니다. –

+1

'std :: runtime_error'는'std :: string'을 사용합니다. –

+0

문자열을 매개 변수로 사용하는 것이 좋습니다. – user1810087

답변

17

기본적으로 조언은 "예외에서 예외를 throw 할 수있는 구문을 사용하지 마십시오"라고 말합니다. 예외를 throw하는 동안 예외가 발생하면 C++ 런타임은 즉시 terminate()을 호출하고 프로그램을 종료하기 때문입니다.

관련된 예외 중 (어느 하나라도) 어쨌든 (어쨌든 예외가 아닌 예외에 대해) terminate()을 호출하면 실제로 걱정할 필요가 없습니다. 예를 들어, 응용 프로그램이 bad_alloc (메모리 부족 상태에서 복구 할 수 없음)을 처리 할 수없는 경우이를 던질 수있는 복사 생성자 (예 : std::string)를 염려 할 필요가 없습니다.

그러나 bad_alloc에서 캐치 및 복구 할 수있게하려면 예외 복사 생성자가 오류를 생성 할 수 없도록해야합니다. 다른 응용 프로그램에서 사용할 라이브러리를 작성하는 경우 응용 프로그램이 bad_alloc을 처리하지 않으려 고 가정해서는 안됩니다.

C++ 11 가능한 경우 복사 생성자 대신 이동 생성자를 사용하면 훨씬 쉽게 만들 수 있습니다. std::string의 이동 생성자는 결코 예외를 throw하지 않으므로 이동 생성자를 올바르게 구현하는 한 안전하게 std:string을 예외 유형으로 사용할 수 있으며 해당 이동자가 사용되는지 확인해야합니다. throw 표현식에서 던져 질 객체의 초기 생성은 예외 발생 프로세스의 일부가 아니므로 생성자는 두 번 예외 (및 종료())없이 예외를 throw 할 수 있습니다. 그래서 만약 당신은 :

throw some_function(); 

some_function가 슬로우하는 객체를 반환하지 않고 (예 : bad_alloc 등의) 예외를 던질 수 있으며 그 괜찮습니다. 예외를 throw하지 않고 유효한 객체를 반환하는 경우 예외 유형에 대한 이동 생성자가 예외 처리 프로세스에 사용되며 이동 생성자가 예외를 throw해서는 안됩니다. 당신이 정확히 한 자리는 가능한 모든 경우에 delete를 호출하거나 (이중 삭제 또는 충돌) 메모리 누수가 있습니다 있는지 확인해야합니다 new 호출 할 때마다


위의 완전히 독립적 인. 이것은 new을 호출 한 다음 예외를 throw 할 수있는 다른 함수 (예 : new)를 다시 호출 할 때마다 까다로워집니다. 생성자에서 이런 일이 발생하면 객체의 소멸자가 호출되지 않습니다 (기본 클래스 및 필드의 소멸자가 있지만). 따라서 예제로 수행하려고하는 것처럼 소멸자에서 정리를 수행 할 수 없습니다.

다행히도 std::unique_ptr이 훨씬 편리합니다. 예외 클래스를 다음과 같이 작성하는 경우 :

struct xexception { 
    std::unique_ptr<int[]> ttt[10]; 
    xexception() { 
    ttt[0].reset(new int[0xfffffffL]); 
    ttt[1].reset(new int[0xfffffffL]); 
    ttt[2].reset(new int[0xfffffffL]); 
    ttt[3].reset(new int[0xfffffffL]); 
    ttt[4].reset(new int[0xfffffffL]); 
    ttt[5].reset(new int[0xfffffffL]); 
    ttt[6].reset(new int[0xfffffffL]); 
    ttt[7].reset(new int[0xfffffffL]); 
    ttt[8].reset(new int[0xfffffffL]); 
    ttt[9].reset(new int[0xfffffffL]); 
    } 
}; 

메모리가 작동하지 않아야합니다.

+0

"안전하게 예외 문자열에 std : string을 사용할 수 있습니다."초기 std :: string'을 구성 (구성)하면 어떨까요? 이것은 예외 (결과적으로 IMHO 문자열의 복사 작업으로'bad_alloc'을 발생시킬 가능성이 높습니다)을 초래할 수 있습니다. – dyp

+3

@DyP : 던져 질 초기 객체를 생성하는 것은 실제로 예외를 던지는 것이 아니므로 'bad_alloc'이 발생하면 이중 예외가 발생하지 않습니다.'terminate()' –

+0

사실,하지만 생각합니다. 'std :: string'을 예외가 아닌 (non-ref, non-ptr) 데이터 멤버로 사용하지 않는 이유는 (생성/작성되어야하기 때문입니다). – dyp

3

핵심 부분에 대해서는 std::string을 사용하지 않는 것이 좋지만 근본적인 예외는 좋은 지침 일 수 있습니다. 사용자 지향 라이브러리/응용 프로그램이 반드시이를 따라야한다고 생각하지 않습니다.

다른 이유가있을 수 있지만 주요 원인은 사용자 또는 개발자에게 상황에 따라 의미있는 정보를 표시하려는 것입니다.이 정보는 단순히 문자 그대로의 문자열로는 할 수없는 경우가 많습니다. 이렇게하려면 동적 할당이 발생해야합니다. 웬일인지 bad_alloc가 있다면, 이미 시작 했으니까요. 그래서 당신에게 아무것도 사거나 잃지 않습니다.

편집 : 그런데

가 : std::exception의 소멸자는 이유로 가상으로 표시됩니다!

+0

문맥 상 의미있는 정보가 문자열에 저장되는 이유는 무엇입니까? 특정 데이터 멤버와 함께 특별히 파생 된 예외 유형을 사용하지 않는 이유는 무엇입니까 (문자열 _here_을 사용해야 할 수도 있습니다)? 예외를 생성하는 동안 문자열을 작성하는 것은 아무도 사용하지 않으면 쓸모가 없으므로 주문형으로 만들지 않으시겠습니까? – dyp

+1

나는 그것이 필요하다는 말은 아니다. 나는 그것이 유용 할 수 있다고 말하고있다. 또한 모든 예외는 어쨌든 효율적으로 동적으로 할당됩니다. (그냥'std :: exception const &'가'MyException const &'가 될 것 같은가?). 예외에 대한 동적 할당은 피해야하지만, 그렇다고 말하는 것은 아닙니다. 중요한 의미있는 오류를 제공하기 위해 문자열을 할당 할만큼 메모리가 충분하지 않다면 다른 곳에서는 문제가있을 수 있습니다. –

+0

"또한 모든 예외가 효율적으로 동적 할당됩니다." 말하자면 - 런타임은 발생할 수있는 모든 가능한 예외의 크기를 링크 시간에 정적으로 알아야합니다. 그리고 확실히 알 수없는 상황이 있습니다. –