c++ - cppreference - noexcept는 언제 사용해야합니까?




dynamic exception specifications are deprecated in c++ 11 (6)

  1. 컴파일러가 스스로 결정할 수없는 많은 함수 예제가 있습니다. 모든 경우 함수 선언에 noexcept를 추가해야합니까?

noexcept 는 함수 인터페이스의 일부이기 때문에 까다 롭습니다. 특히 라이브러리를 작성하는 경우, 클라이언트 코드는 noexcept 특성에 의존 할 수 있습니다. 기존 코드를 손상시킬 수 있으므로 나중에 변경하기가 어려울 수 있습니다. 애플리케이션에 의해서만 사용되는 코드를 구현할 때 이는 그리 우려 할 사항이 아닐 수 있습니다.

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

반면에 함수가 결코 던져서는 안되며 그것이 스펙의 일부라는 것이 noexceptnoexcept 선언해야 noexcept . 그러나 구현이 변경되면 컴파일러는 noexcept 위반을 감지 할 수 없습니다.

  1. noexcept의 사용에 대해 어떤 상황에서 더주의해야합니까? 그리고 어떤 상황에서 내재 된 noexcept (false)를 없앨 수 있습니까?

가장 큰 영향을 미칠 수 있기 때문에 집중해야하는 네 가지 클래스의 함수가 있습니다.

  1. 작업 이동 (할당 연산자 이동 및 생성자 이동)
  2. 스왑 운영
  3. 메모리 할당 취소 (연산자 삭제, 연산자 삭제 [])
  4. 소멸자 ( noexcept(true) 하지 않으면 암묵적으로 noexcept(true) )입니다.

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

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

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

예외 지정이나 throw() 에 대한 noexcept 의 장점은 컴파일러가 unwinding을 스택 할 때 더 많은 자유를 허용한다는 것입니다. throw() 경우조차도 컴파일러는 스택을 완전히 풀어야합니다 (객체 구조의 역순으로 수행해야합니다).

반면에 noexcept 경우, 그렇게 할 필요는 없습니다. 스택을 풀어야한다는 요구 사항은 없습니다 (그러나 컴파일러는 여전히이를 수행 할 수 있습니다). 이 자유는 스택을 풀 수있는 오버 헤드를 줄이므로 코드 최적화를 더 많이 허용합니다.

noexcept, stack unwinding 및 성능 에 관한 관련 질문은 스택 풀기가 필요할 때 오버 헤드에 대한 자세한 내용으로 이어집니다.

나는 Scott Meyers의 책 "Effective Modern C ++", "Item 14 : 더 많은 독서를 위해 그들이 예외를 내지 않을지라도 함수를 선언 할 것"을 추천한다.

noexcept 키워드는 많은 함수 시그니처에 적절히 적용될 수 있지만 실제로 사용하는 것을 고려해야 할 때는 확실하지 않습니다. 지금까지 읽은 내용을 토대로 noexcept 막판 추가는 이동 생성자가 던질 때 발생하는 몇 가지 중요한 문제를 해결하는 것으로 보입니다. 그러나, 나는 처음부터 noexcept 에 대해 더 많은 것을 읽게하는 실질적인 질문에 대해 만족할만한 답을 제공 할 수 없다.

  1. 컴파일러가 스스로 결정할 수없는 많은 함수 예제가 있습니다. 모든 경우 함수 선언에 noexcept 를 추가해야합니까 ?

    모든 함수 선언 후에 noexcept 를 추가 할 필요가 있는지에 대해 생각해 noexcept 프로그래머의 생산성이 크게 noexcept 것입니다 (솔직히 엉덩이에 고통이 될 것입니다). noexcept 의 사용에 대해 어떤 상황에서 더주의해야합니까? 그리고 어떤 상황에서 내재 된 noexcept(false) 있습니까?

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

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


컴파일러가 스스로 결정할 수없는 많은 함수 예제가 있습니다. 모든 경우 함수 선언에 noexcept를 추가해야합니까?

"나는 그들이 [결코] 던지지 않을 것임을 알고있다"고 말할 때, 함수가 던지지 않는 함수의 구현을 검사하면됩니다. 나는 그 접근법이 내부라고 생각합니다.

함수가 예외를 throw하여 함수의 일부로 포함시킬 수 있는지 여부를 고려하는 것이 좋습니다. 인수 목록만큼 중요하고 메서드가 mutator (... const )인지 여부입니다. "이 함수는 결코 예외를 throw하지 않습니다"라는 선언은 구현에 대한 제약 사항입니다. 이것을 생략해도 함수가 예외를 throw 할 수있는 것은 아닙니다. 함수의 현재 버전 모든 이후 버전이 예외를 throw 할 수 있음을 의미합니다. 구현을 더 어렵게 만드는 것은 제약 조건입니다. 그러나 어떤 방법은 실제로 유용하다는 제약이 있어야합니다. 가장 중요한 것은 소멸자로부터 호출 할 수 있지만 강력한 예외 보장을 제공하는 메서드에서 "롤백"코드를 구현하는 것입니다.


나는 그것을 실제로 사용하는 데 충분한 시간이 없었기 때문에 이것을위한 "모범 사례"답변을주는 것이 너무 이른 것이라고 생각한다. 그들이 던져지기 직후 던지기 지정자에 대해 질문을 받았다면 지금과는 매우 다른 답변을 얻을 수 있습니다.

모든 함수 선언 후에 noexcept 를 추가 noexcept 여부에 대해 생각해 noexcept 프로그래머의 생산성을 크게 떨어 뜨릴 수 있습니다 (솔직히 말해서 고통이 될 것입니다).

그런 다음 함수가 결코 throw되지 않는다는 것이 확실 할 때 사용하십시오.

noexcept 를 사용하면 성능 향상을 noexcept 있습니까? [...] 개인적으로, 나는 어떤 종류의 최적화를 안전하게 적용하기 위해 컴파일러에 제공되는 증가 된 자유 때문에 noexcept 관심이있다.

가장 큰 최적화 이득은 noexcept 를 검사하고 과부하가 noexcept 때문에 컴파일러가 아닌 사용자 최적화에 따른 것 같습니다. 대부분의 컴파일러는 예외가없는 경우 예외 처리 방법을 따르지 않으므로 처리의 제거로 바이너리 크기를 줄이지 만 코드의 시스템 코드 수준에서 많은 부분이 변경 될지는 의문입니다. 암호.

큰 4 ( noexcept 가 이미있는 것처럼 생성자, 할당, 소멸자가 noexcept 일 때 할당)에서 noexcept 를 사용하면 std 컨테이너와 같은 템플릿 코드에서 noexcept 검사가 '공통' noexcept 때문에 최상의 개선이 이루어질 수 있습니다. 예를 들어, std::vectornoexcept 로 표시되어 있지 않으면 클래스 이동을 사용하지 않습니다 (그렇지 않으면 컴파일러가 달리 추론 할 수 있음).


내가 요즘 되풀이하고있는 것처럼 : 의미론이 우선 이다.

noexcept 추가 noexcept , noexcept(true)noexcept(false) 는 가장 중요한 의미론입니다. 부수적으로 가능한 최적화의 조건을 설정합니다.

프로그래머가 코드를 읽으 noexcept , noexcept 의 존재는 const 의 존재와 유사하다 : 그것은 나에게 일어날 수도 있고 일어나지 않을 수도있는 일을 더 잘 돕는다. 따라서 함수가 던져 지는지 여부를 아는 지에 대해 생각해 보는 것이 좋습니다. 기억을 위해서는 어떤 종류의 동적 메모리 할당이 던질 수 있습니다.

자, 이제 가능한 최적화에 대해 설명합니다.

가장 확실한 최적화가 실제로 라이브러리에서 수행됩니다. C ++ 11은 함수가 noexcept 인지 여부를 알 수있게 해주는 많은 특성을 제공하며 표준 라이브러리 구현 자체는 가능하면 noexcept 를 사용하여 사용자가 정의한 객체에 대해 noexcept 연산을 선호합니다. 이동 의미론 과 같은.

컴파일러는 예외 처리 데이터에서 약간의 뚱뚱한 부분 (아마) 만 깎을 수 있습니다. 거짓말했을 가능성을 고려해야하기 때문입니다. noexcept 라고 표시된 함수가 throw하면 std::terminate 가 호출됩니다.

이러한 의미는 두 가지 이유로 선택되었습니다.

  • 의존성이 이미 그것을 사용하지 않을 때에도 noexcept 로부터 즉각적으로 이익을 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
}

barnoexcept 로 레이블되면이 함수의 흐름 그래프가 달라집니다 ( bar 끝과 catch 문 사이에서 실행이 noexcept 뛸 수있는 방법이 없음). noexcept 라는 레이블을 noexcept 컴파일러는 baz 함수 중에 x 값이 5임을 확신합니다. x = 5 블록은 bar() 에서 catch 문까지의 가장자리없이 baz (x) 블록을 "지배"한다고합니다. 그런 다음보다 효율적인 코드를 생성하기 위해 "상수 전파"라는 것을 할 수 있습니다. 여기서 baz가 인라인된다면, x를 사용하는 문장은 상수를 포함 할 수 있으며, 런타임 평가로 사용 된 것은 컴파일 타임 평가 등으로 바뀔 수 있습니다.

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


noexcept 는 일부 작업의 성능을 크게 향상시킬 수 있습니다. 이것은 컴파일러에 의해 기계 코드를 생성하는 레벨에서 발생하는 것이 아니라 가장 효과적인 알고리즘을 선택함으로써 발생합니다 : 언급 된 다른 것들처럼 함수 std::move_if_noexcept 사용하여이 선택을합니다. 예를 들어, std::vector (예 : reserve 를 호출 할 때)의 성장은 강력한 예외 안전 보장을 제공해야합니다. T 의 이동 생성자가 던지지 않는다는 것을 안다면 모든 요소를 ​​이동할 수 있습니다. 그렇지 않으면 모든 T 를 복사해야합니다. 이 내용은 이 게시물 에 자세히 설명되어 있습니다.







noexcept