noexcept에 관한 정리

noexcept가 하는 일과 코드에 영향을 미치는 부분에 관한 정리

Posted by Start Bootstrap on January 14, 2019

C++에서는 함수가 예외를 처리하는 방법이 2가지 있습니다. 함수가 예외를 던질 가능성이 있는지 아니면 절대 예외를 던지 않는지. 두번째의 경우 즉 절대 예외를 던지지 않는 경우 C++에서 noexcept 키워드를 사용하여 설정 할 수 있습니다.

noexcept 설정

        
            
            // c++98 방식 현재 권장받지 않는 방식
            void ExceptFunction(argument...) throw();

            // c++11 방식 사용하는 것을 권장 받는 방식
            void ExceptFunction(argument...) noexcept;

        
    

C++에서 noexcept를 사용하게 되면 어떤 일이 일어날까요. Compiler의 대부분의 Optimization의 경우 "Flow Graph"라는 것을 사용해서 합법적으로 추론합니다. noexcept의 경우 해당 그래프를 변경할 수 있습니다. 예를 들어 예외 발생 상황 뒤에 값을 할당했다면 해당 Flow에 대한 다양한 결과가 있을 것입니다. 왜냐하면 예외를 해서 종료했을 수 있으니까요. 하지만 noexcept인 경우 해당 값을 확신할 수 있습니다. 이런 경우 효율적인 코드를 생성하기 위해 "Constant Propagation"을 수행합니다. 그러면 인라인의 혜택을 보는 함수가 있을 수 있습니다. 또한 스콧 마이어스에 따르면 noexcept 함수의 경우 optimizers가 run stack에서 unwindable state을 유지할 필요가 없어집니다. 따라서 생성의 반대 순서로 파괴할 필요가 없기 때문에 컴파일러를 느슨하게 만들어서 최적화 면에서 유연성을 가질 수 있습니다.

그리고 C++11에서 move semantics에 관한 문제를 해결할 때 도움이 됩니다. Douglas Gregor와 David Abrahams이 설명한 것(참조 : http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2009/n2855.html#problem)에 따르면 push_back에 대한 경우를 한번 살펴보겠습니다. vector의 size와 capacity가 같은 경우에는 더 많은 저장 공간을 할당해야합니다 이 경우 먼저 저장소를 더 할당 한 다음 이전 저장소의 내용을 새 저장소로 "이동" 합니다. 마지막으로 새 요소를 새 저장소에 복사하고 모든 것이 성공하면 이전 저장소를 해제합니다. std::move이전 저장소의 요소를 rvalues로 처리하고 이동 생성자(정의되어 있는 경우)를 활성화하거나 같은 구문으로 복사 생성자(이동 생성자를 사용할 수 없는 경우)로 다시 폴링합니다. 만약 이동 중에 예외가 발생한 경우 복사 생성자는 원본 데이터를 수정하지 않았기 때문에 복구가 가능합니다. 하지만 vector에 저장된 유형이 이동 생성자를 제공하면 각 값은 이전 저장 영역에서 새 저장 영역으로 이동되어 잠재적으로 이전 저장 영역의 값을 변경합니다. 만약 요소의 이동 생성자가 예외를 던질 수 없는 경우 초기 메모리 할당 이후 아무 작업도 수행 할 수 없기 때문에 새 저장소 초기화가 성공한 것으로 보장됩니다. 하지만 요소의 이동 생성자가 예외를 던질 수 있는 경우 벡터의 상태가 변경된 후 예외가 발생할 수 있으며 벡터의 원래 상태를 복구 할 수 있는 확실한 방법이 없습니다. 그래서 컴파일러는 이동 연산자가 예외를 방출하지 않음이 확실한 경우에만 복사 연산자를 이동 연산자를 대체합니다. 이는 잠재적인 성능 향상이 있습니다.

작성중... 소멸자는 noexcept이다.