gugbab2's GitBook
  • Language
    • C++
      • 강의
        • C++ 언매니지드 프로그래밍
          • C++ 프로그래밍
          • 출력(Output)
          • 입력(Input)
          • bool 타입, Reference
          • 상수(const)
          • 문자열(string)
          • 파일 입출력
          • 개체지향 프로그래밍1
          • 개체지향 프로그래밍2
          • 개체지향 프로그래밍3
          • 캐스팅(형변환, casting)
          • 인라인 함수
          • static 키워드
          • 예외(Exception)
          • STL(Standard Template Library) 컨테이너(Container) - Vector
          • STL 컨테이너 - Map
          • STL 컨테이너 - Queue, Stack, Set, List
          • 템플릿(Template) 프로그래밍
          • 새로운 키워드(C++11 ~) 1
          • 새로운 키워드(C++11 ~) 2
          • 새로운 자료형
          • 새로운 STL 컨테이너
          • 스마트(smart) 포인터
          • 이동생성자 및 이동대입연산자
          • constexpr
          • Lamda Expression
      • 책
        • The C++ Programming Lanuaage
          • 2부 : 기본 기능
            • 6. 타입과 선언
            • 7. 포인터, 배열, 참조
            • 8. 구조체(struct), 공용체(union), 열거형(enum)
            • 10. 표현식
            • 11. 선택 연산
            • 12. 함수
            • 13. 예외 처리
            • 15. 소스 파일과 프로그램
          • 3부 : 추상화 메커니즘
            • 16. 클래스
            • 17. 생성, 소멸, 복사와 이동
            • 18. 연산자 오버로딩
            • 19. 특수 연산자
            • 20. 파생클래스
        • 씹어먹는 C++
          • 2. C++ 참조자(reference) 의 도입
          • 5.1 연산자 오버로딩(비교, 대입 연산자)
          • 5-2. 연산자 오버로딩(이항, 입출력, 타입변환, 증감 연산자)
          • 6-2. 가상(virtual) 함수와 다형성
          • 6-3. 가상 함수에 대한 지식들
          • 9-1. 코드를 찍어내는 틀 - C++ 템플릿(template)
          • 9-2. 가변 길이 템플릿(Variadic template)
          • 9-3. 템플릿 메타 프로그래밍 (Template Meta Programming)
          • 9-4. 템플릿 메타 프로그래밍2
          • 16.1 유니폼 초기화(Uniform Initialization)
          • 토막글 2. 람다(lambda)
    • Java
      • 강의
        • 김영한의 실전 자바 - 기본편
          • 절차 지향 vs 객체 지향
            • 절차 지향 프로그래밍
            • 객체 지향 프로그래밍
          • 변수
            • 클래스 변수 / 인스턴스 변수, 멤버 변수 / 지역 변수
            • 기본형 vs 참조형
          • 패키지
            • 패키지
            • CLI 환경에서 .java 파일 컴파일 && 실행
          • 접근 제어자
            • 접근 제어자 - 기본
            • 캡슐화
          • static
            • 자바 메모리 구조
            • static 기본
            • 스택 영역, 힙 영역
              • 스택 영역, 힙 영역 - 기본
              • 메소드가 실행될 때 어떤일이 일어나는가?
          • 상속
            • 상속 기본
          • 다형성(Pilymorphism)
            • 다형성 기본
            • 다형성의 활용
              • 다형성의 활용 - 기본
              • 다형성의 활용 - 추상클래스
              • 다형성의 활용 - 인터페이스
            • 다형성과 설계
              • 좋은 객체 지향 프로그래밍
        • 김영한의 실전 자바 - 중급1편
          • 1. Object 클래스
          • 2. 불변 객체
          • 3. String 클래스
          • 4. 래퍼, Class 클래스
          • 5. 열거형 - ENUM
          • 6. 날짜와 시간
          • 7. 중첩 클래스, 내부 클래스1
          • 8. 중첩 클래스, 내부 클래스2
          • 9. 예외 처리1 - 이론
          • 10. 예외 처리 - 실습
        • 김영한의 실전 자바 - 중급2편
          • 1. 제네릭 - Generic1
          • 2. 제네릭 - Generic2
          • 3. 컬렉션 프레임워크 - ArrayList
          • 4. 컬렉션 프레임워크 - LinkedList
          • 5. 컬렉션 프레임워크 - List
          • 6. 컬렉션 프레임워크 - 해시(Hash)
          • 7. 컬렉션 프레임워크 - HashSet
          • 8. 컬렉션 프레임워크 - Set
            • 레드 블랙 트리
          • 9. 컬렉션 프레임워크 - Map, Stack, Queue
            • 왜(?) Set 은 내부에서 Map 을 사용할까?
          • 10. 컬렉션 프레임워크 - 순회, 정렬, 전체 정리
        • 김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성
          • 프로세스와 스레드 소개
          • 스레드 생성과 실행
          • 스레드 제어와 생명 주기1
          • 스레드 제어와 생명 주기2
          • 메모리 가시성
          • 동기화 - synchronized
            • synchronized 키워드 이해도 체크
          • 고급 동기화 - concurrent.Lock
          • 생산자 소비자 문제1
          • 생산자 소비자 문제2
          • CAS - 동기화와 원자적 연산
          • 동시성 컬렉션
          • 스레드 풀과 Executor 프레임워크1
          • 스레드 풀과 Executor 프레임워크2
        • 김영한의 실전 자바 - 고급 2편, I/O, 네트워크, 리플렉션
          • 문자 인코딩
          • I/O 기본1
          • I/O 기본2
          • I/O 활용
          • File, Files
          • 네트워크 - 프로그램1
          • 네트워크 - 프로그램2
          • 채팅 프로그램
          • HTTP 서버 만들기
          • 리플렉션
          • 애노테이션
          • HTTP 서버 활용
        • 김영한의 실전 자바 - 고급3편, 람다, 스트림, 함형 프로그래밍
          • 람다가 필요한 이유
          • 람다
          • 함수형 인터페이스
          • 람다 활용
          • 람다 vs 익명 클래스
          • 메서드 참조
          • 스트림API1 - 기본
          • 스트림 API2 - 기능
          • 스트림 API3 - 컬렉터
          • Optional
          • 디폴트 메서드
          • 병렬 스트림
          • 함수형 프로그래밍
        • 기초 탄탄! 독하게 시작하는 Java - Part2: OOP 와 JVM
          • 2. 클래스 - 첫 번째
          • 3. 클래스 - 두번째
          • 4. 상속과 관계
          • 6. JVM(Java Virtual machine) 기본 이론
          • 7. JVM 과 GC 그리고 객체
          • 8. 불변 객체와 String 클래스
      • 책
        • 자바의 신
          • 변수
            • 클래스 변수(static) 사용 주의 케이스
            • Java volatile 과 Atomic 변수(+CAS)
          • 연산자
            • 비트 연산자 활용 예제
          • 배열
          • 참조 자료형
          • 상속
          • Object 클래스
          • interface, abstract class, enum
          • 예외
          • String 클래스
            • String 구조
            • String 문자열을 byte 로 변환하기
            • String 클래스에서 자주 사용되는 메서드
            • String 클래스로 살펴보는 불변(Immutable)객체
            • StringBuilder, StringBuffer
          • Nested 클래스
          • 어노테이션
            • 어노테이션 기본
            • 어노테이션의 사용
          • JVM 이해하기
            • 왜 JVM 을 사용해?
            • JVM, JRE, JDK
            • JVM 구조 이해하기
            • 클래스 로더 시스템
            • JIT(Just-In-Time) 컴파일러
            • GC(Garbage Collector)
              • GC Part.1
              • GC Part.2
              • GC 튜닝
          • java.lang
            • Wrapper 클래스
            • System 클래스
          • Generic
            • 제네릭 기본
            • 와일드카드
            • 와일드카드 GET / SET 경계
            • 와일드카드 extends / super 사용시기
            • 혼동할 수 있는 와일드카드 표현
          • Collection
            • 자료구조
              • 이진 탐색 트리 vs 레드 블랙 트리
            • Collection
            • List
              • ArrayList
              • Vector
              • Stack
              • LinkedList
            • Set, Queue
              • HashSet
              • LinkedHashSet
              • TreeSet
              • Priority Queue
              • ArrayDeque
            • Map
              • HashMap
              • Hashtable
              • LinkedHashMap
              • TreeMap
          • Thread
            • Thread 기본
            • Thread 와 관련이 많은, Synchronized
            • Thread 를 통제하는 메서드
            • ThreadGroup
          • I/O
            • InputStream, OutputStream
            • Reader, Writer
          • Serializable, NIO
            • Serializable
            • NIO (New IO)
          • 네트워크 프로그래밍
            • 네트워크 기본 & TCP 통신
            • UDP 통신
          • 람다
            • 함수형 인터페이스
            • 람다란?
        • 벨둥(Bealdung)
          • Java Concurrency
            • Java Concurrency Basics
              • Overview of the java.util.concurrent
              • Guide to the Synchronized Keyword in Java
              • Guide to the Volatile Keyword in Java
              • Guide to the java.util.concurrent.Future
              • ThreadLocal in Java
      • 그 외
        • 시스템 콜과 자바에서의 시스템 콜 사용례
        • 자바 NIO 의 동작원리 및 IO 모델
        • 함수형 인터페이스(FunctionInterface) - 자바8
  • Spring
    • 강의
      • 스프링 핵심 원리 - 기본편
        • 큰 흐름 잡기
        • 스프링 핵심 원리 이해1 - 예제 만들기
        • 스프링 핵심 원리 이해2 - 객체 지향 원리 적용
        • 스프링 컨테이너와 스프링 빈
        • 싱글톤 컨테이너
        • 컴포넌트 스캔
        • 의존관계 자동 주입
        • 빈 생명주기 콜백
        • 빈 스코프
      • 토비의 스프링6 - 이해와 원리
        • 3. 오브젝트와 의존관계1
        • 3. 오브젝트와 의존관계2
        • 4. 테스트
        • 5. 템플릿
        • 6.예외
        • 7. 서비스 추상화
    • 책
      • JSP 2.3 웹 프로그래밍
        • Servlet
        • JSP
        • 쿠키 / 세션
        • MVC 패턴
        • 실무 때 고민할 만한 부분
      • 스프링 입문을 위한 자바 객체지향의 원리와 이해
        • 자바와 절차적/구조적 프로그래밍
        • 객체지향의 4대 특성
        • 객체지향 설계의 5원칙
        • 스프링이 사랑한 디자인 패턴
        • IoC / DI
        • AOP(Aspect Oriented Programming), 관점 지향 프로그래밍
      • 토비의 스프링 3.1
        • Spring vs Spring Boot
        • 1. 오브젝트와 의존관계
          • 1.4 제어의 역전(IoC)
          • 1.5 스프링의 IoC
          • 1.6 싱글톤 레지스트리와 오브젝트 스코프
    • 그 외
      • 스프링 부트(SpringBoot) 탄생 배경
  • CS
    • DATA STRUCTURES
      • 선택 정렬(Selection Sort)
      • 버블 정렬(Bubble Sort)
      • 삽입 정렬(Insertion Sort)
    • OS
      • 강의
      • 책
        • 혼자 공부하는 컴퓨터구조 + 운영체제
          • 1. 컴퓨터 구조 시작하기
          • 2. 데이터
          • 3. 명령어
          • 4. CPU 의 작동원리
          • 5. CPU 성능 향상 기법
          • 6. 메모리와 캐시메모리
          • 7. 보조기억장치
          • 8. 입출력장치
          • 9. 운영체제 시작하기
          • 10. 프로세스와 스레드
    • NETWORK
      • 그 외
        • REST API
          • REST API
          • URI & MIME type
          • Collection Pattern
          • Collection Pattern 적용
          • Spring Web MVC 구현
        • SSL 인증 동작
        • DTO & JSON & CROS
          • DTO
          • 직렬화(Serialization)
          • Jackson ObjectMapper
          • CROS
        • Connection Timeout / Read Timeout
      • 강의
        • 외워서 끝내는 네트워크 핵심이론 - 기초
          • Internet 기반 네트워크 입문
            • Host 는 이렇게 외우자
            • 스위치가 하는 일과 비용
          • L2 수준에서 외울 것들
            • NIC, L2 Frame, LAN 카드 그리고 MAC 주소
            • L2 스위치에 대해서
            • LAN 과 WAN 의 경계 그리고 Broadcast
          • L3 수준에서 외울 것들
            • IPv4 주소의 기본 구조
            • L3 IP Packet 으로 외워라
            • 패킷의 생성과 전달 및 계층별 데이터 단위
            • 이해하면 인생이 바뀌는 TCP/IP 송, 수신 구조
            • IP 헤더 형식
            • 서브넷 마스크와 CIDR
            • Broadcast IP 주소와 Localhost
            • TTL 과 단편화
            • 인터넷 설정 자동화를 위한 DHCP
            • ARP 과 Ping(RTT : Round Trip Time)
          • L4 수준 대표주자 TCP 와 UDP
            • TCP 와 UDP 개요
            • TCP 연결 및 상태 변화
            • TCP 연결 종료 및 상태 변화
            • TCP, UDP 헤더 형식과 게임서버 특징
            • TCP 가 연결이라는 착각
            • TCP 연결과 게임버그
          • 웹을 이루는 핵심기술
            • DNS
            • URL, URI
        • 외워서 끝내는 네트워크 핵심 이론 - 응용
          • 네트워크 장치의 구조
            • 세 가지 네트워크 장치 구조
            • Inline 구조
            • Out of path 구조와 DPI 그리고 망중립
            • Proxy(클라이언트 입장) - 우회
            • Proxy(클라이언트 입장) - 보호와 감시
            • Reverse Proxy(서버 입장)
          • 인터넷 공유기의 작동 원리
            • 공유기 개요
            • Symmetric NAT
            • Full Cone 방식
            • Restricted Cone, Port Restricted Cone
            • 포트 포워딩
            • UPnP 와 NAT
          • 부하분산 시스템 작동 원리
            • L4 부하분산 무정지 시스템
            • 대규모 부하분산을 위한 GSLB
          • VPN과 네트워크 보안 솔루션
            • PN 과 VPN
            • IPSec VPN 과 터널링 개념
            • VPN 과 재택근무
        • 외워서 끝내는 SSL 과 최소한의 암호기술
          • 기초이론
            • Checksum (검사합)
            • Hash
          • 암호기술에 대한 이해
            • 대칭키
            • 비대칭키
          • PKI 시스템과 인터넷
            • 인터넷을 위한 비대칭키 체계
            • 공개키 신뢰를 위한 검증체계
            • 웹서비스와 공인인증서
      • 책
        • 그림으로 배우는 네트워크 원리
          • 1. 네트워크 기본
          • 2. 네트워크를 만드는 것
          • 3. 네트워크의 공통 언어 TCP/IP
    • SECURITY
      • 그 외
        • Basic Auth
        • HMAC 기반 인증
    • 그 외
      • 동기/비동기 & 블로킹/논블록킹
  • DB
    • 그 외
      • 인덱스(Index)
      • 트랜잭션(TRANSACTION)
      • 실무에서 외래키를 사용하지 않는 이유
      • ORM vs SQL Mapper
      • 문자열 vs DATE
      • EXPLAIN 명령어
    • 강의
      • Real MySQL 시즌 1
        • Part.1
          • 1강. CHAR vs VARCHAR
          • 2강. VARCHAR vs TEXT
          • 3강. COUNT(*) & COUNT(DISTINCT) 튜닝
          • 4강. 페이징 쿼리 작성
          • 5강. Stored Function
      • 토크온 41차. JPA 프로그래밍 기본 다지기
        • 1. JPA 소개
        • 2. JPA 기초와 매핑
        • 3. 필드와 컬럼 매핑
        • 4. 연관관계 매핑
        • 5. 양방향 매핑
        • 6. JPA 내부구조
        • 7. JPA 객체지향쿼리
        • 8. Spring Data JPA 와 QueryDSL 이해
    • 책
  • Software Development Methodology
    • TDD
      • 강의
        • Spring Boot TDD - 입문부터 실전까지 정확하게
          • 세션2. TDD 소개
          • 세션5. API 설계
          • 세션6. TDD 주기 첫 번째 경험
          • 세션7. TDD 주기 반복
      • 그 외
        • 단위 테스트(Unit Test) 작성의 필요성
        • JUnit5
          • A Guide to JUnit 5
          • Guide to JUnit 5 Parameterized Tests
          • AssertJ Exception Assertions
          • Testing in Spring Boot
          • Junit 과 Mockito 기반의 Spring 단위 테스트 코드 작성법
        • Code Coverage
          • Code Coverage?
    • DDD
      • 책
        • 도메인 주도 설계(Domain-Driven Design)
          • 04 - 도메인의 격리
          • 05 - 소프트웨어에서 표현되는 모델
          • 06 - 도메인 객체의 생명주기
          • 07 - 언어의 사용(확장 예제) (1)
          • 07 - 언어의 사용(확장 예제) (2)
        • 도메인 주도 개발 시작하기
          • 1. 도메인 모델 시작하기
          • 2. 아키텍처 개요
          • 3. 애그리거트
          • 4. 리포지터리와 모델 구현
            • DAO vs Repository
      • 강의
        • DDD 세레나데(NEXTSTEP)
          • 1주차
            • 도메인 주도 설계 등장 배경
            • 레거시 코드
            • 유연한 설계 - ASSERTION
          • 2주차
            • 전략적 설계 - UBIQUITOUS LANGUAGE
            • 전략적 설계 - BOUNDED CONTEXT
          • 3주차
            • 전술적 설계 - VALUE OBJECT 와 ENTITY
            • 전술적 설계 - AGGREGATE 와 REPOSITORY
            • 전술적 설계 - SERVICE
    • REFACTORING
      • 일급 컬렉션(First Class Collection) 소개와 사용해야하는 이유
  • ARCHITECTURE
    • Event Driven Architecture
  • 멘토링
    • F-Lab
      • 10회차(2024.12.29)
Powered by GitBook
On this page
  • 13.1 오류 처리
  • 13.1.1 예외
  • 13.1.2 전통적인 예외 처리
  • 13.1.3. 버티기
  • 13.1.4 예외의 다른 관점
  • 13.1.6. 계층적인 예외 처리
  • 13.1.7 예외와 효율성
  • 13.2 예외 보장
  • 13.3 자원 관리
  • 13.1.1 Finally
  • 13.4 불변속성의 강제
  • 13.5 예외 던지기와 잡기
  • 13.5.1. 예외 던지기
  • 13.5.2 예외 잡기
  • 13.6 vector 구현
  1. Language
  2. C++
  3. 책
  4. The C++ Programming Lanuaage
  5. 2부 : 기본 기능

13. 예외 처리

13.1 오류 처리

13.1.1 예외

  • 예외(exception) 이란 개념은 오류가 탐지된 시점에서 오류가 처리될 수 있는 시점으로 정보를 전달하는 데 도움을 주기 위해 제공된다.

  • 전형적인 예제는 아래와 같다.

void taskmaster()
{
    try{
        auto result = do_task();
        // 결과를 사용한다. 
    }
    catch(Some_error){
        // do_task 실행 실패 - 문제를 처리한다. 
    }
}

int do_task()
{
    // ...
    if(/*테스크를 수행할 수 없다면*/)
        return result;
    else
        throw Some_error{};
}
  • 기본적으로 예외는 특정 목적을 위해 특별히 정의된 사용자 정의 타입만을 사용하기를 권장한다. -> 명확한 의도전달!

struct Range_error {}; 
void f(int n)
{
    if (n<0 || max<n) throw Range_error{}; 
    ...
}

13.1.2 전통적인 예외 처리

  • 프로그램 종료!(꽤나 과격한 방법..) -> 계속적으로 돌아가는 프로그램에서는 사용 불가한 방법

if(something_wrong) exit(1);
  • 오류 처리 함수를 호출한다.

    • 오류 처리 함수를 통해서 프로그램이 종료되지 않고 로직이 계속될 수 있다.

if(something_wrong) something_handler();    // 오류 처리 함수를 통해서 로직이 계속 될 수 있다. 

13.1.3. 버티기

  • 프로그램이 종료를 받아들일 수 없는 상황이라면 모든 예외를 붙잡아 둘 수 있다. -> 결국 예외는 프로그래머가 허용한 경우에만 프로그램을 종료한다.

  • 때때로 사람들은 오류 메시지를 출력하고, 사용자에게 도움을 요청하는 대화상자를 만드는 등의 방식으로 '버티기' 의 부정적인 측면들을 완화시키려고 노력한다.

13.1.4 예외의 다른 관점

13.1.4.1 비동기적 이벤트

  • 예외 처리 매커니즘은 동기적은 예외만을 처리하게 설계되어 있다.

  • 비동기적 이벤트는 예외 처리를 사용할 수 없기 때문에, 근본적으로 다른 매커니즘을 필요로 한다.

  • 많은 시스템은 비동기적 예외를 처리하기 위한 매커니즘 신호와 같은 것들을 지원해서 비동기적 예외를 처리한다.

13.1.4.2 오류가 아닌 예외

  • 예외를 '시스템의 어떤 부분에서 요청된 작업을 처리할 수 없는 것' 이라고 생각해보자.

  • 예외(throw) 는 함수 호출에 비해서 드물에 이루어져야 한다. -> 그렇지 않은 경우 시스템 구조가 모호해진다.

  • 프로그램 동작에 나쁜 영향을 끼치지 않는 예외의 경우, 단지 프로그래머는 그것을 오류라고 간주하고 예외처리 매커니즘을 오류를 처리하는 도구 정도로 생각한다. -> 누군가는 예외 처리 매커니즘을 그저 또 하나의 제어 구조이자 호출자에게 값을 반환하는 다른 방법 정도로 생각할 수 있다.

  • '예외처리는 오류처리' 라는 관점에 집중해야 한다. -> 그 외 관점의 예외처리는 용도에 맞지 않게 사용하고 있는 것이다.

13.1.6. 계층적인 예외 처리

  • 예외 처리의 목적은 프로그램의 한 부분이 다른 부분에게 요청된 작업이 수행될 수 없다는 사실을 알려주기 위한 수단을 제공하는 것이다.

  • 프로그램의 다양한 부분이 예외 처리되는 방식과 예외가 처리될 장소에 대한 정의가 되어 있어야 한다. -> 예외 처리 매커니즘은 기본적으로 비지역적으로 종합적인 전략을 정의하는 것이 필수적이다. -> 간단하고 명료해야 한다.

  • 규모가 큰 프로젝트에서 예외 처리 전략을 상세하게 정의하고 지키기는 어려운 일이다.. -> 때문에, 범용적으로 사용할 수 있는 예외처리 전략을 세우는 일은 중요한 일이다! -> 범용적이고, 계층적인 예외 처리 전략을 필수적이다.

13.1.7 예외와 효율성

  • 예외 던지기는 함수 호출처럼 비용이 많이 들지 않게끔 구현될 수 있다. -> 쉬운일은 아니다.. -> 하지만, 예외 처리가 아닌 다른 대안 역시 공짜는 아니다!

  • 예외 처리와 관련 없어 보이는 아래 함수를 확인해보자.

    • g() 나 h() 는 예외를 던질 수 있으므로, f() 는 예외가 일어날 경우 buf 를 제대로 소멸시킬 수 있는 코드를 탑재해야 한다.

void f()
{
    string buf;
    cin>>buf;
    ...
    g(1);
    h(buf);
}    
  • 신중하고 체계적인 오류 처리가 필요할 때는 예외 처리 매커니즘을 사용하는 것이 최선이다.

  • noexcept 키워드를 사용하게 되면 컴파일러에게 해당 함수는 예외를 던지지 않는 것을 알려주기 때문에, 요긴하게 사용될 수 있다.

    • 전통적인 C 함수는 예외를 던지지 않으므로 대부분의 C 함수는 noexcept 를 사용할 수 있다.

    • 하지만, C 함수 내부에서 C++ 의 new 연산자를 사용해 bad_alloc 을 던질수도 있다.. -> 늘 그렇듯 측정이 수반되지 않은 효율성 논의는 무의미하다.

13.2 예외 보장

C++ 표준 라이브러리는 모든 라이브러리 연산 내 예외에 대해서 다음 중 하나를 보장한다.

C++ 표준 라이브러리가 여러 수준의 예외 안정성을 제공하는 이유

  • 예외 발생시에도 프로그램이 일관된 상태를 유지

  • 리소스 누수 등 심각한 문제가 발생하지 않도록

  • 기본 보장(basic guarantee)

    • 일관된 상태 유지 : 표준 라이브러리의 대부분의 구성 요소는 예외가 발생하더라도 개체가 일관된 상태를 유지하도록 설계되었다.

    • 리소스 누수 방지 : 표준 라입브러리는 예외 발생 시 리소스가 누수되지 않도록 보장한다.

    • 아래 코드에서 vec 의 상태는 구체적으로 알 수 없다.. -> 예외가 발생하기 전의 상태, 즉 '{1,2,3}' 일수도 있고, -> 예외가 발생하기 직전의 상태인, 즉 '{1,2,3,4}' 일수도 있다. -> 구체적으로 어떤 상태인지 예측할 수 없지만, vec 는 항상 유효한 상태를 유지한다.

void exampleFunction()
{
    std::vector<int> vec = {1,2,3};
    try{
        vec.push_back(4);        // 예외가 발생할 수 있다. 
    } catch(...) {
         // 예외가 발생해도 vec 는 일관된 상태를 유지한다. 
         // 하지만, vec 는 여전히 일관된 상태지만, 내용은 알 수 없다.
    }
}
  • 강한 보장(strong guarantee)

    • 롤백 : 일부 표준 라이브러리의 구성 요소는 예외가 발생하면 함수 호출 이전 상태로 되돌릴 수 있는 강한 보장을 제공한다.

std::vector<int> vec = {1,2,3};
std::vector<int> temp = vec;    // 임시 복사본 생성
try {
    temp.push_back(4);    // 예외 발생 가능성 있음 
    vec = temp;           // 성공하면 vec 에 적용
} catch (...) {
    // vec 는 상태가 변경되지 않는다. 
}
  • 예외 없음 보장(nothrow guarantee)

    • 예외를 던지지 않음 : 표준 라이브러리의 일부 함수는 예외를 던지지 않음을 보장한다. 주로 소멸자, 메모리 할당 / 해제 함수 등에서 사용된다.

void noThrowFunction() noexcept {
    // 예외를 던지지 않는 함수
    try {
        // 안전한 코드 작성 
    } catch (...) {
        // 모든 예외를 잡아 처리
    }
}

Basic gaurantee, Strong guarantee 는 기본적으로 자원 누출이 없을 것을 요구한다.

13.3 자원 관리

  • 함수에서 파일, 힙 메모리, 뮤텍스 등을 획득할 때는 해당 자원이 반납되는 것이 필수적이다.

  • 다음과 같이 catch 문을 통해서 자원을해제할 수 있다.

void use_file(const char* fn)
{
    FILE* f = fopen(fn, "r");
    try {
        // ... use f...
    }
    catch (...) {
        fclose();
        throw;
    }
    fclose(f); 
}
  • 하지만 위와 같은 방식은 코드 복잡성과 유지보수 측면에서 더 많은 비용이 들 수 있다. -> 예외 처리의 단점..

  • 때문에, 아래와 같이 생성자와 소멸자를 가진 클래스를 개체를 통해 처리할 수 있다. ( RAII 방식) -> File_ptr 을 생성하는 과정에서 예외가 발생하게 된다면 RAII 에 의해 File_ptr 의 소멸자가 호출된다.

RAII(Resource Acqusition Is Initialization)

  • RAII 의 기본 개념

    • C++ 의 중요한 프로그래밍 기법 중 하나로, 자원의 획득과 해제를 개체의 생명 주기와 결부시키는 것을 의미한다.

  • RAII 의 동작 원리

    • 개체 생성 시 자원 획득 : 개체가 생성되면 생성자를 통해 자원을 얻는다. -> 생성자가 호출되기 이전까지는 개체가 생성되지 않은 것이다!

    • 예외 발생 시 자원 해제 : 개체를 사용하는 함수 내에서 예외가 발생한다면, Stack Unwinding 이 시작되어 개체의 소멸자가 호출된다.

    • 개체 소멸 시 자원 해제 : 개체가 스코프를 벗어나면 소멸자가 호출된다.

class File_ptr {
    FILE* p;
public : 
    File_ptr(const char* n, const char* a)    // 파일 n을 연다
        : p{fopen(n,a)}
    {
        if(p==nullptr) throw runtime_error("File_ptr : Can't open file");
    }
    File_ptr(const string& n, const char* a)  // 파일 n을 연다.
        : File_ptr{n.c_str(), }
    {}
    explict File_ptr(FILE*pp)
        : p{pp}
    {    
        if(p==nullptr) throw runtime_error("File_ptr : nullptr");
    }    
    // ...
    ~File_ptr() {fclose(p);}
    operator File*() {return p;}
};

// ..

void use_file(const char* fn)
{
    File_ptr f(fn, "r");
    // ... f를 사용한다. 
}    

13.1.1 Finally

  • Finally 은 예외 이후의 마무리를 담당하는 임의의 코드를 작성하는 방식으로 RAII 에 비해서 단점이 있어 사용을 권장하지는 않는다.

  • Finally 에 비해 RAII 의 장점은 다음과 같다. -> 결론적으로 RAII 방식은 자원 관리가 개체의 생명 주기와 결합되어 있어, 코드가 더 간결하고 예외 안정성이 높으며, 유지보수가 용이하다.

  1. 코드의 간결성과 가독성

    • RAII: 자원의 획득과 해제가 객체의 생명 주기에 묶여 있기 때문에, 자원 해제 코드가 분리되지 않고 객체의 소멸자에 포함됩니다. 이는 코드의 가독성을 높이고, 자원 관리를 명확하게 합니다.

    • finally 블록: 자원을 해제하는 코드가 각기 다른 위치에 흩어질 수 있습니다. 특히 자원을 해제해야 하는 다양한 코드 경로가 있는 경우, finally 블록을 반복적으로 작성해야 할 수 있습니다.

  2. 예외 안전성

    • RAII: 예외가 발생하더라도 객체의 소멸자가 자동으로 호출되므로, 자원이 안전하게 해제됩니다. 이로 인해 리소스 누수를 방지할 수 있습니다.

    • finally 블록: 예외가 발생한 경우에도 finally 블록에서 자원을 해제할 수 있지만, 여러 개의 자원을 관리해야 하는 경우 각 자원에 대해 별도의 try-finally 블록을 작성해야 할 수 있습니다. 이는 코드가 복잡해질 수 있습니다.

  3. 유지보수성

    • RAII: 자원 관리가 객체의 생명 주기와 결합되어 있어, 자원의 획득과 해제가 명확하고 일관성 있게 이루어집니다. 이를 통해 유지보수가 용이해집니다.

    • finally 블록: 자원 해제 코드가 여러 곳에 분산되어 있을 수 있으며, 새로운 예외 처리 경로를 추가할 때 모든 관련 finally 블록을 수정해야 할 수 있습니다.

13.4 불변속성의 강제

  • 함수의 선행조건이 충족되지 않을 경우 함수는 제대로 수행될 수 없다. -> 선행조건이란 매개변수를 의미한다.

  • 불변 속성이란 개체가 유효한 상태로 존재하기 위해서 만족해야 하는 상태 혹은 조건이다.

  • 생성자가 클래스의 불변속성을 만족시키지 않는다면 개체는 사용될 수 없다. -> 개체가 예측할 수 없는 상태 변화가 있을 수 있고, 이는 코드의 안정성과 신뢰성을 저해할 수 있기 때문이다.

  • 위 2가지 문제가 있을 경우, 아래와 같은 3가지 접근 법을 섞어서 사용한다.

    • 예외 처리

    • 그냥 아무것도 하지 않는다. -> 선행 조건을 만족시키는 것은 호출자의 몫이고, 그대로 내버려두게 되면 개선된 설계, 테스트를 통해 시스템에서 오류가 사라지게 될 것이다.

    • 프로그램을 종료한다. -> 선행 조건 위반은 심각한 설계 오류로 프로그램이 실행되지 않아야 한다.

  • 희망하는 조건과 불변속성을 체크파기 위해서 범용적으로 assert 기법이 사용된다. assert 의 조건이 true이라면 체크하지 않지만, false 라면 아무런 코드도 생성되지 않는다.

  • <cassert> 에서 assert 를 제공하면 종류는 다음과 같다.

    • assert : 컴파일 타임, 런타임 조건을 모두 체크한다.

    • static_assert : 컴파일 타임 조건을 체크한다.

13.5 예외 던지기와 잡기

13.5.1. 예외 던지기

  • 복사되거나, 이동될 수 있는 모든 타입의 예외를 throw 할 수 있다.

  • 잡힌 개체는, 원칙상으로 던져진 개체의 사본이다. (최적화 매커니즘이 복사를 최소화하긴 하지만..) -> throw x; 는 x 타입의 임시 변수를 x 로 초기화한다. (이러한 임시 변수는 잡히기 전까지 몇 번 더 추가적으로 복사될 수 있다)

  • 아래 throw No_copy(); 에서는 기본 생성자가 호출되고, C++ 의 예외처리 시스템은 스택을 넘어 안전하게 전파되어야 한다. 이를 위해 개체를 복사하거나 이동되어야 하는데, 복사 생성자가 delete 로 선언되어 있어서 사용할 수 없다. -> 때문에, throw No_copy(); 는 컴파일 에러가 발생한다.

class No_copy{
    No_copy(const No_copy&) = delete;
};

class My_error {
    // ...
}

void f(int n)
{
    switch(n) {
        case 0 : throw My_error();    // ok
        case 1 : throw No_copy();     // error : No_copy 를 복사할 수 없다.
        case 2 : throw My_error;      // error : My_error 는 개체가 아니라 타입이다. 
    }    
}
  • 예외를 던진 시점에서 스택을 거슬러 올라가면서 이전에 호출된 함수들에 대한 작업을 취소하고 스택 프레임을 제거하는 stack unwindding 이 발생한다. -> 이 때 함수에서 사용된 개체들의 소멸자가 호출된다.

  • 아래 코드에서, h() 에서 던진 후에 생성된 모든 string 은 생성된 순서 역순으로 소멸된다. "not", "or", "excess", "in" 은 소멸되지만, 제어 스레드가 다다르지 못한 "all all" 은 소멸되지 않으며, 영향을 받지 않았던 "Byron" 도 소멸되지 않는다.

void f()
{
    string name {"Byron"};
    try {
        string s = "in";
        g();
    }
    catch(My_error){
        // ...
    }
}
void g()
{
    string s = "excess";
    {
        string s = "or";
        h();
    }
}
void h()
{
    string s = "not";
    throw My_error();
    string s2 = "at all";
}
  • runtime_error 나, out_of_range 등의 표준 라이브러리 예외 클래스는 생성자 인자로 문자열 인자를 받아들이며, 해당 문자를 다시 출력할 함수 what() 을 가지고 있다.

struct My_error2 : std::runtime_error {
    const char* what() const noexcept {return "My_error2";}
}

void g(int n)
{
    if(n)
        throw std::runtime_error{"I give up!"};
    else 
        throw My_error2{};
}

void f(int n)
{
    try {
        void g(n);
    }
    catch (std::exception& e) {
        cerr << e.what() <<'\n';
    }
}

13.5.1.1 noexcept 함수

  • 일부 함수는 예외를 던지지 않으며, 실제로 일부는 예외를 던지지 말아야 한다.

  • 이를 나타내기 위해서 그런 함수를 noexcept 로 선언할 수 있다.

double compute(double) noexcept;    // 예외를 던지지 않을 것이다!
  • noexcept 로 선언하는 것은 어떤 프로그램에 대해 추론하려는 프로그래머와 어떤 프로그램을 최적화하려는 컴파일러에게 가장 유용한 기능이다. -> 프로그래머는 try 절을 제공하려 신경쓰지 않아도 되고, -> 컴파일러의 최적화 매커니즘은 예외 처리로 발하는 제어 경로에 대해 신경쓰지 않아도 된다.

  • 하지만 noexcept 는 컴파일러와 링커에 의해 완전히 체크되지 않는다. 예를 들어 vector 생성자가 자신의 데이터를 저장하기 위한 메모리를 획득하는데 실패해서 std::bad_alloc 을 던지게 되면 이 경우 프로그램은 std::terminate() 를 호출함으로 무조건적으로 종료된다.

  • noexcept 가 throw에 대응하게 작성되지 않았다는 점을 명시하는 것 만으로도 의미가 있다.

13.5.1.2 noexcept 연산자

  • 아래 코드의 의미는 noexcept<Is_pod<T>()> 는 함수 Is_pod<T>() 가 true 이면 my_fct 함수는 예외를 던지지 않지만, false 라면 my_fct 함수는 예외를 던진다는 의미이다.

  • 일반적인 noexcept 는 noexcept(true) 를 의미한다.

template<typename T>
void my_fec(T& x) noexcept(Is_pod<T>());

13.5.2 예외 잡기

  • 원칙적으로 예외는 던져질 때 복사된다. -> 던져진 예외를 catch 까지 안전하게 전달하기 위해서, 개체의 복사 or 이동이 발생하게 된다.

  • catch 는 다음과 같은 경우 호출된다.

    • H 가 E 와 같은 타입인 경우

    • H 가 E 의 명맥한 공개 기반 타입인 경우

    • 포인터 or 참조자인 경우 위 조건이 유효한 경우

void f()
{
    try {
        throw E{}; 
    } catch(H) {
        
    }
}

13.5.2.1 다시 예외 던지기

  • 예외는 붙잡았지만, 핸들러가 오류를 완벽하게 처리할 수 없다고 판단하는 것은 흔한 일이다. -> 그런 경우 핸들러에서 대개 지역적으로 처리할 수 있는 일을 수행하고 나서, 예외를 다시 던진다.

  • 그에 따라서 오류는 가장 적합한 곳에서 처리될 수 있다.

13.5.2.2 모든 예외 잡기

  • std::exception 은 표준 라이브러리의 예외를 전부 잡는다. 하지만, 표준 라이브러리 예외는 단 하나의 예외 타입 집합이다. -> 따라서, std::exception& 도 모든 예외를 잡을수는 없다. -> int 나 특정 계층 구조에서 등장하는 예외를 던진다면, std::exception& 에 대한 핸들러에 의해 잡히지 않을 것이다.

  • 모든 예외를 잡고 싶다면, ... 를 사용해서 모든 예외를 붙잡는다 라는 의미로 사용할 수 있다.

void m()
{
    try {
        // 뭔가를 한다. 
    }
    catch(std::exception& err) {    // 모든 표준 라이브러리 예외를 처리한다. 
        // ...
        throw;
    }
}

13.5.2.3 다중 헤더

  • try 블록은 여러개의 catch 를 가질 수 있다.

  • 파생 된 예외가 하나 이상의 예외 타입에 대한 핸들러들에 의해 잡힐 수 있으므로, try 문에서 핸들러가 작성 된 순서가 중요하다.

void f()
{
    try{
    
    } catch (std::ios_base::failure) {
        // 특정 예외 처리
    } catch (std::exception& e) {
        // 표준 라이브러리 예외 처리 
    } catch ( ... ) {
        // 모든 예외 처리
    }
}

13.6 vector 구현

Previous12. 함수Next15. 소스 파일과 프로그램

Last updated 1 year ago