언제 실제로 noexcept를 사용해야합니까? 언제 사용해야하는지 잘 모르겠습니다. 지금까지 읽은

noexcept키워드는 많은 함수 시그니처에 적절하게 적용될 수 있지만 실제로 키워드를 언제 사용해야하는지 잘 모르겠습니다. 지금까지 읽은 내용을 기반으로 마지막 순간 추가는 noexcept이동 생성자가 던질 때 발생하는 몇 가지 중요한 문제를 해결하는 것으로 보입니다. 그러나 여전히 실용적인 질문에 대한 만족스러운 답변을 제공 할 수 없어서 noexcept처음부터 더 자세히 읽을 수 있었습니다.

  1. 필자가 결코 알지 못할 함수의 많은 예가 있지만 컴파일러가 스스로 결정할 수는 없습니다. 그러한 모든 경우noexcept 에 함수 선언에 추가해야합니까 ?

    모든 함수 선언 noexcept후에 추가해야하는지에 대해 생각해야하는 것은 프로그래머의 생산성을 크게 떨어 뜨릴 것입니다. 어떤 상황에서 사용에 대해 더주의를 기울여야하며 , 어떤 상황에서 묵시적으로 벗어날 수 있습니까?noexceptnoexcept(false)

  2. 언제 사용 후 성능 향상을 실제로 볼 수 noexcept있습니까? 특히, C ++ 컴파일러가를 추가 한 후 더 나은 기계 코드를 생성 할 수있는 코드의 예를 제공하십시오 noexcept.

    개인적으로, noexcept특정 종류의 최적화를 안전하게 적용하기 위해 컴파일러에 제공되는 자유가 증가했기 때문에 관심이 있습니다. 최신 컴파일러 noexcept는 이런 방식으로 활용 합니까? 그렇지 않다면, 그들 중 일부는 가까운 시일 내에 그렇게 할 것으로 기대할 수 있습니까?



답변

실제로 사용하기에 충분한 시간이 없었기 때문에 “모범 사례”에 대한 답변을하기에는 너무 이르다고 생각합니다. 이것이 던져진 직후 던져 지정자에 대해 묻는다면 그 대답은 지금과 매우 다릅니다.

noexcept모든 함수 선언 후에 추가해야하는지에 대해 생각해야 한다면 프로그래머의 생산성이 크게 떨어질 것입니다 (솔직히 고통 스럽습니다).

그렇다면 함수가 절대로 throw되지 않는 것이 분명 할 때 사용하십시오.

언제 사용 후 성능 향상을 실제로 볼 수 noexcept있습니까? 개인적으로, 나는 noexcept특정 종류의 최적화를 안전하게 적용하기 위해 컴파일러에 제공되는 자유가 증가했기 때문에 신경 을 쓴다.

확인 noexcept및 과부하 가능성으로 인해 컴파일러 최적화가 아닌 사용자 최적화에서 가장 큰 최적화 이점을 얻는 것 같습니다 . 대부분의 컴파일러는 페널티가없는 경우 예외 처리 방법을 따르지 않으므로 코드의 기계 코드 수준에서 많은 (또는 무엇이든) 변경 될지 의심 스럽습니다. 취급 코드.

사용 noexcept빅 4에 (이미있는 한 생성자, 할당되지 소멸자 noexcept로 가능성이 가장 개선의 원인이됩니다) noexcept검사는 같이 템플릿 코드에서 ‘일반’이다 std용기. 예를 들어, std::vector클래스가 표시되어 있지 않으면 클래스의 이동을 사용하지 않습니다 noexcept(또는 컴파일러가 다른 방법으로 추론 할 수 있음).


답변

요즘 계속 반복하면서 의미론이 먼저 .

추가 noexcept, noexcept(true)그리고 것은 noexcept(false)무엇보다도 의미에 대한 것입니다. 우연히 많은 가능한 최적화를 조건화합니다.

프로그래머가 코드를 읽는 것처럼 존재하는 noexcept것과 비슷합니다 const. 발생하거나 발생하지 않는 것을 더 잘 이해하는 데 도움이됩니다. 따라서 함수가 발생할지 여부를 생각하는 데 시간을 투자하는 것이 좋습니다. 다시 말해, 모든 종류의 동적 메모리 할당이 발생할 수 있습니다.


이제 가능한 최적화로 넘어갑니다.

가장 확실한 최적화는 실제로 라이브러리에서 수행됩니다. C ++ 11은 함수의 유무를 알 수있는 여러 가지 특성을 제공하며 noexcept표준 라이브러리 구현 자체는 해당 특성을 사용하여 noexcept가능한 경우 조작하는 사용자 정의 오브젝트에 대한 조작 을 선호 합니다. 이동 의미 와 같은 .

이 때문에 컴파일러는 데이터에만 예외 처리에서 지방 (아마)의 비트를 면도 할 수 있다 계정으로 당신이 거짓말을 할 수 있다는 사실을 고려 할 수 있습니다. 표시된 함수 noexcept가 throw되면 std::terminate호출됩니다.

이 의미론은 다음 두 가지 이유로 선택되었습니다.

  • noexcept종속성이 아직 사용하지 않는 경우에도 즉시 혜택 (역 호환성)
  • noexcept이론적으로 던질 수 있지만 주어진 인수에 대해 예상되지 않는 함수를 호출 할 때 의 스펙을 허용

답변

이것은 실제로 컴파일러의 옵티 마이저와 (잠재적으로) 큰 차이를 만듭니다. 컴파일러는 실제로 함수 확장 이후의 빈 throw () 문과 속성 확장을 통해이 기능을 몇 년 동안 가지고있었습니다. 최신 컴파일러가이 지식을 활용하여 더 나은 코드를 생성한다는 것을 확신 할 수 있습니다.

컴파일러의 거의 모든 최적화는 합법적 인 것에 대해 추론하기 위해 함수의 “흐름 그래프”라는 것을 사용합니다. 플로우 그래프는 일반적으로 함수의 “블록”이라고하는 것 (단일 입구와 단일 출구가있는 코드 영역)과 플로우가 이동할 수있는 위치를 나타내는 블록 사이의 에지로 구성됩니다. Noexcept는 플로우 그래프를 변경합니다.

구체적인 예를 요청했습니다. 이 코드를 고려하십시오.

void foo(int x) {
    try {
        bar();
        x = 5;
        // Other stuff which doesn't modify x, but might throw
    } catch(...) {
        // Don't modify x
    }

    baz(x); // Or other statement using x
}

이 함수에 대한 플로우 그래프 bar는 레이블이 지정된 경우 다릅니다 noexcept(실행 bar문 끝 과 catch 문 사이를 이동할 수있는 방법이 없습니다 ). 로 레이블이 지정되면 noexcept컴파일러는 baz 함수 중 x의 값이 5임을 확신합니다. x = 5 블록은 bar()catch 문 에서 가장자리없이 baz (x) 블록을 “지배”한다고 합니다.

그런 다음보다 일정한 코드를 생성하기 위해 “일정한 전파”라는 것을 수행 할 수 있습니다. 여기서 baz가 인라인되면 x를 사용하는 명령문에도 상수가 포함될 수 있으며 런타임 평가에 사용되었던 것을 컴파일 타임 평가 등으로 바꿀 수 있습니다.

어쨌든, 짧은 대답 : noexcept컴파일러가 더 엄격한 흐름 그래프를 생성하게하고 흐름 그래프는 모든 종류의 일반적인 컴파일러 최적화에 대해 추론하는 데 사용됩니다. 컴파일러에게는 이러한 특성의 사용자 주석이 훌륭합니다. 컴파일러는이 물건을 알아 내려고 시도하지만 일반적으로 할 수 없습니다 (문제의 함수는 컴파일러에 보이지 않는 다른 객체 파일에 있거나 보이지 않는 일부 함수를 전 이적으로 사용할 수 있습니다). 당신이 알지도 못했을 수도있는 사소한 예외가 있으므로 암시 적으로 레이블을 지정할 수 없습니다 noexcept(예 : 할당 메모리는 bad_alloc을 던질 수 있음).


답변

noexcept일부 작업의 성능을 크게 향상시킬 수 있습니다. 컴파일러에서 머신 코드를 생성하는 수준에서는 발생하지 않지만 가장 효과적인 알고리즘을 선택하면됩니다. 다른 언급했듯이 function을 사용하여이 선택을 수행 std::move_if_noexcept합니다. 예를 들어 std::vector(예 : 전화 할 때) 의 증가 reserve는 강력한 예외 안전 보장을 제공해야합니다. T의 이동 생성자가 던지지 않는다는 것을 알고 있다면 모든 요소를 ​​이동할 수 있습니다. 그렇지 않으면 모든 Ts 를 복사해야합니다 . 이에 대해서는 이 게시물 에서 자세히 설명했습니다 .


답변

사용 후 성능 향상을 관찰하는 것 외에는 현실적으로 언제 할 수 noexcept있습니까? 특히, noexcept를 추가 한 후 C ++ 컴파일러가 더 나은 기계 코드를 생성 할 수있는 코드의 예를 제공하십시오.

음, 절대? 시간이 없습니까? 못.

noexcept을위한 컴파일러 같은 방법으로 성능 최적화 const컴파일러 성능 최적화를위한 것입니다. 즉, 거의 없습니다.

noexcept함수가 예외를 던질 수있는 경우 컴파일 타임에 “사용자”가 감지 할 수 있도록하는 데 주로 사용됩니다. 기억하십시오 : 대부분의 컴파일러는 실제로 무언가를 던지지 않는 한 예외에 대한 특수 코드를 생성하지 않습니다. 그래서 noexcept함수를 최적화하는 방법에 대한 컴파일러 힌트를주는 너무 많이로 제공의 문제가 아니라 당신이 함수를 사용하는 방법에 대한 힌트.

같은 템플릿 move_if_noexcept은 이동 생성자가 정의되어 있는지 감지하고 그렇지 않은 경우 유형 대신 대신을 noexcept반환합니다 . 그렇게하는 것이 매우 안전하다면 움직이라는 말입니다.const&&&

일반적으로 noexcept실제로 유용한 것으로 생각 될 때 사용해야합니다 . is_nothrow_constructible해당 유형에 해당하는 경우 일부 코드는 다른 경로 를 사용합니다. 그렇게하는 코드를 사용하는 경우 noexcept적절한 생성자를 자유롭게 사용하십시오 .

한마디로 : 이동 생성자와 유사한 구성에 사용하십시오. 그러나 그 구성 요소에 신경 쓸 필요는 없습니다.


답변

에서 비얀 의 단어 ( 는 C ++의 언어, 제 4 판 프로그래밍 , 페이지 366)

종료가 수용 가능한 응답 인 경우 종료되지 않은 예외는 terminate () 호출 (§13.5.2.5)이되기 때문에이를 달성 할 수 있습니다. 또한 noexcept지정자 (§13.5.1.1)는 그러한 요구를 명시 적으로 만들 수 있습니다.

성공적인 내결함성 시스템은 다단계입니다. 각 수준은 너무 왜곡되지 않고 가능한 많은 오류를 처리하고 나머지는 더 높은 수준으로 둡니다. 예외는 그 관점을 지원합니다. 또한
terminate()예외 처리 메커니즘 자체가 손상 되었거나 불완전하게 사용 된 경우 이스케이프를 제공하여이보기를 지원 하므로 예외가 포착되지 않습니다. 마찬가지로,
noexcept복구하려고 시도 할 수없는 오류에 대한 간단한 탈출을 제공합니다.

double compute(double x) noexcept;     {
    string s = "Courtney and Anya";
    vector<double> tmp(10);
    // ...
}

벡터 생성자는 10 더블에 대한 메모리를 확보하지 못하고를 던질 수 std::bad_alloc있습니다. 이 경우 프로그램이 종료됩니다. 호출하면 무조건 종료됩니다 std::terminate()(§30.4.1.3). 소멸자가 함수 호출을 호출하지 않습니다. throw와 사이의 범위에서 소멸자 noexcept가 호출 되는지 여부는 구현 정의입니다
(예 : compute ()의 경우). 프로그램은 곧 종료 될 것이므로 어떤 객체에도 의존해서는 안됩니다. 지정자 를 추가함으로써 noexcept, 우리는 코드가 드로우에 대처하도록 작성되지 않았 음을 나타냅니다.


답변

  1. 필자가 결코 알지 못할 함수의 많은 예가 있지만 컴파일러가 스스로 결정할 수는 없습니다. 그러한 모든 경우에 함수 선언에 noexcept를 추가해야합니까?

noexcept함수 인터페이스의 일부이므로 까다 롭습니다. 특히 라이브러리를 작성하는 경우 클라이언트 코드가 noexcept속성에 따라 달라질 수 있습니다 . 기존 코드가 손상 될 수 있으므로 나중에 변경하기 어려울 수 있습니다. 응용 프로그램에서만 사용하는 코드를 구현할 때는 걱정할 필요가 없습니다.

던질 수없는 함수가 있다면, 유지를 원하는지 noexcept또는 향후 구현을 제한 할 것인지 스스로에게 물어보십시오 . 예를 들어 예외 (예 : 단위 테스트)를 발생시켜 잘못된 인수에 대한 오류 검사를 수행하거나 예외 사양을 변경할 수있는 다른 라이브러리 코드에 의존 할 수 있습니다. 이 경우 보수적이고 생략하는 것이 더 안전합니다 noexcept.

반면에, 함수가 절대로 던져지지 않아야하고 그것이 사양의 일부인 것이 정확하다고 확신하는 경우이를 선언해야합니다 noexcept. 그러나 noexcept구현이 변경 될 경우 컴파일러는 위반을 감지 할 수 없습니다 .

  1. 어떤 상황에서 noexcept의 사용에 대해 더주의를 기울여야하며, 어떤 상황에서 묵시적인 noexcept (false)로 벗어날 수 있습니까?

가장 큰 영향을 미치기 때문에 집중해야 할 네 가지 기능 클래스가 있습니다.

  1. 이동 작업 (할당 연산자 이동 및 생성자 이동)
  2. 스왑 작업
  3. 메모리 할당 해제 기 (운영자 삭제, 운영자 삭제 [])
  4. 소멸자 ( noexcept(true)당신이 그들을 만들지 않는 한 이것들은 암시 적으로 있지만 noexcept(false))

이러한 함수는 일반적으로이어야 noexcept하며 라이브러리 구현 noexcept에서이 속성 을 사용할 수 있습니다 . 예를 들어, std::vector강력한 예외 보장을 희생하지 않고 비 투척 이동 작업을 사용할 수 있습니다. 그렇지 않으면 (C ++ 98에서와 같이) 복사 요소로 폴백해야합니다.

이러한 종류의 최적화는 알고리즘 수준에 있으며 컴파일러 최적화에 의존하지 않습니다. 특히 요소를 복사하는 데 비용이 많이 드는 경우 큰 영향을 줄 수 있습니다.

  1. noexcept를 사용한 후 성능 향상을 현실적으로 볼 수있는 시점은 언제입니까? 특히, noexcept를 추가 한 후 C ++ 컴파일러가 더 나은 기계 코드를 생성 할 수있는 코드의 예를 제공하십시오.

noexcept예외 사양을 사용하지 않거나 throw()스택을 풀 때 표준에 따라 컴파일러에서 더 많은 자유를 얻을 수 있다는 이점이 있습니다. 이 throw()경우 에도 컴파일러는 스택을 완전히 풀어야합니다 (그리고 객체 구성의 정확한 역순으로해야합니다).

에서 noexcept경우에, 다른 한편으로는 그렇게 할 필요가 없습니다. 스택을 풀어야 할 필요는 없습니다 (그러나 컴파일러는 여전히 그렇게 할 수 있습니다). 이러한 자유로 인해 스택을 항상 풀 수있는 오버 헤드가 줄어들 기 때문에 코드 최적화가 더욱 강화됩니다.

noexcept, 스택 해제 및 성능 에 대한 관련 질문은 스택 해제가 필요한 경우 오버 헤드에 대한 자세한 정보를 제공합니다.

자세한 내용은 Scott Meyers의 “Effective Modern C ++”책, “항목 14 : 예외가 발생하지 않는 경우를 제외하고 함수 선언”을 참조하십시오.