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
  • 12.1 함수 선언
  • 12.1.1 왜 함수인가?
  • 12.1.2 함수 선언문의 구성
  • 12.1.4 값 반환
  • 12.1.5 inline 함수
  • 12.1.6 constexpr 함수
  • 12.1.7 [[noreturn]] 함수
  • 12.1.8 지역 변수
  • 12.2 인자 전달
  • 12.2.1 참조자 인자
  • 12.2.2 배열 인자
  • 12.2.3 리스트 인자
  • 12.2.4 인자의 개수가 지정되지 않은 경우
  • 12.3 오버로딩 함수
  • 12.3.1 자동 오버로딩 해결
  • 12.3.2 오버로딩과 반환 타입
  • 12.3.5 수동 오버로딩 해결
  • 12.5 함수를 가리키는 포인터
  1. Language
  2. C++
  3. 책
  4. The C++ Programming Lanuaage
  5. 2부 : 기본 기능

12. 함수

12.1 함수 선언

12.1.1 왜 함수인가?

  • 함수의 목적은 복잡한 계산을 의미있는 덩어리로 쪼개고, 그것들에 이름을 붙이자는 것이다.

  • 이는 함수의 가독성을 높이자는 것이고, 이는 유지보수를 편하게 하기 위한 첫걸음이다.

  • 코드에서 오류의 개수는 코드의 양과 코드의 복잡성과 강력한 상관관계를 갖는데, -> 두 문제 모두 더 짧은 함수를 많이 사용함으로 해결할 수 있다.

  • 가장 기본적인 조언은 한 화면에서 전체를 볼 수 있는 정도로 함수의 크기를 유지하라는 것이다. -> 많은 프로그래머들에게 함수당 대략 40줄 정도의 함수를 만들라고 이야기한다. -> 필자가 이상적으로 생각하는 수준은 7줄이다.

  • 실질적으로 거의 모든 경우 함수 호출 비용은 문제의 원인이 아니다. -> 호출이 많아져 호출 비용의 문제가 생길 경우 인라인 함수를 사용해 비용을 줄일 수 있다.

12.1.2 함수 선언문의 구성

  • 호출되는 모든 함수는 어딘가에서 선언 및 정의되야 하며, 함수 정의는 함수의 본체가 표시되는 함수의 선언이다.

  • 다음을 보자

void swap(int*, int*);    // 선언
void swap(int*p, int*q);    // 정의
{
    ...
}
  • 또는, 아래와 같이 선언과 정의의 매개변수의 이름이 다를수도 있다. -> 필수는 아니지만, 가독성을 위해서 일치시킨다.

int& max(int& a, int& b, int& c);
int& max(int& x1, int& x2, int& x3)
{
    ...
}
  • 아래와 같이, 이름 없는 인자를 사용할 수도 있다. -> 이름을 붙이지 않을때는, 해당 인자가 함수 정의에서 사용되지 않는다는 점을 나타낸다. -> 이는 코드를 간단히 하기 위한 목적이나, 확장을 염두해 둔 사전 설계 때문에 등장한다.

void seach(table* t, const char* key, const char*)
{
    // 세번째 인자는 사용되지 않는다. 
    ...
}

12.1.4 값 반환

  • 전통적으로 함수 선언에서 반환 타입이 맨 앞에 위치한다.

  • 하지만, 함수 선언은 반환 타입을 매개변수 뒤에 위치시키는 문법을 이용해서 사용될 수도 있다.

  • 아래 두 선언은 동일하다.

string to_string(int a);            // 전위형 반환 타입(전통적 방식)
auto to_string(int a) -> string;    // 후위형 반환 타입
  • 후위형 반환 타입이 사용될 때는, 함수 템플릿에서 중요해진다.

  • 아래 코드를 보자 -> 람다와 비슷하게 생겼다.

template<typename T, typename U>
auto product(const vector<T>& x, const vector<U>& y) -> decltype(x*y);
  • 매번 함수가 호출될 때마다, 스택에 변수가 할당된다.

  • 함수가 반환된 후 해당 공간은 재사용되므로, 지역변수를 가리키는 포인터는 절대로 반환되지 않아야한다. -> 참조나 포인터가 아닌 유의미한 값이 반환되어야 한다.

  • 다행스럽게도 컴파일러는 지역변수에 대한 참조자 반환에 대해서 경고를 보낸다.

  • 아래 코드는 좋지 않은 예

int* fp()
{
    int local = 1;
    ...
    return &local;    // Bad
}

int & fr()
{
    int local = 1;
    ...
    return local    // Bad
}

함수를 빠져나가는 5가지 방법

  1. return 문의 실행

  2. 단순하게 함수의 끝에 도달

  3. 지역적으로 잡히지 않는 예외를 던진다.

  4. 예외가 던져지고 noexcept 에서 지역적으로 잡히지 못하는 것으로 인한 종료

  5. 반환하지 않는 시스템 함수(ex, exit()) 를 직간접적으로 호출

12.1.5 inline 함수

  • inline 함수는 컴파일러에 의해 함수 호출이 해당 함수 본문의 내용을 대체되는 것이다. -> 인라인 함수는함수 호출의 오버헤드를 줄이고 실행 속도를 높일 수 있다.

  • 내용이 너무 길 경우에는 코드의 크기가 증가하고, 캐시의 많은 메모리가 사용될 수 있다. 또한 함수를 변경할 때마다 모든 호출 지점이 다시 컴파일되어야 하기 때문에, 내용이 너무 길 경우에는 인라인 함수를 사용하지 않는다.

  • 함수는 아래와 같이 inline 으로 정의될 수 있다.

    • inline 지정자는 해당 함수가 인라인 호출 코드를 생성하려 시도한다는 것을 컴파일러에게 알려주는 중요한 단서이다.

    • inline 지정자를 선언해도 무조건적으로 인라인화가 되는 것은 아니다. 컴파일러가 판단한다.

    • 어떤 값이 반드시 컴파일 타임에 계산되게 하고 싶다면, 해당 값을 constexpr 로 선언하고 해당 값의 평가에 사용될 모든 함수를 constexpr 로 만들어야 한다.

constexpr inline int fec(constexpr int n)
{
    return (n<2) ? 1 : n*fac(n-1);
}

12.1.6 constexpr 함수

  • 일반적으로 함수는 컴파일 타임에 평가될 수 없으므로 상수 표현식에 호출될 수 없다.

  • 하지만, constexpr 함수로 지정하면 해당 함수가 상수 표현식 안에서 사용되게 만들 수 있다. -> 컴파일 타임에 초기화식을 평가하겠다는 뜻이다.

constexpr int fac(int n)
{
    return (n>1) ? n*fac(n-1) : 1;
}
constexpr int f9 = fac(9);    // 컴파일 타임에 평가되어야 한다. 
void f(int n) 
{
     int f5 = fac(5);               // 컴파일 타임에 평가될 수 있다.
     int fn = fac(n);               // n은 변수이기 때문에, 런타임에 평가된다.
 
     constexpr int f6 = fac(6);     // 컴파일 타임에 평가될 수 있다. 
     constexpr int fnn = fac(n);    // error : n은 변수이기 때문에, 컴파일 타임 평가를 보장할 수 없다. 
     
     char a[fac(4)];                // 배열의 경계는 상수여야하고, fac() 는 constexpr 이다. 
     char a2[fac(n)];               // error : 배열의 경계는 상수여야하는데, n은 변수이기 때문에 에러.
}
  • 컴파일 타임에 평가되려면 함수는 충분히 간단해야 한다. (constexpr 은 순수 함수)

  • 때문에 다음과 같은 제약조건을 가지게 된다.

    1. 단순한 표현식:

      • constexpr 함수는 기본적으로 단순한 표현식을 포함해야 합니다. 복잡한 런타임 로직을 포함할 수 없습니다.

    2. 한정된 함수 본문:

      • 함수 본문은 한정된 수의 명령문과 연산만을 포함할 수 있습니다. 특히 컴파일 타임에 평가될 수 있어야 합니다.

      • 조건문 (if), 반복문 (for, while) 등을 사용할 수 있지만, 이들 역시 컴파일 타임에 평가될 수 있어야 합니다.

    3. 상수 표현식만 사용:

      • 함수 내에서 사용되는 모든 변수와 객체는 상수 표현식이어야 합니다.

      • constexpr 함수 내부에서 비상수 데이터나 포인터 조작은 허용되지 않습니다.

    4. 반환 타입:

      • 함수의 반환 타입은 literal type이어야 합니다. 기본적으로 기본 자료형과 constexpr로 정의된 사용자 정의 타입이 이에 해당합니다.

    5. 함수 호출:

      • constexpr 함수는 다른 constexpr 함수나 상수 표현식에서 호출될 수 있습니다.

int glob;
constexpr void bad1(int a)        // error : constexpr 함수는 void 일 수 없다.
{
    glob = a;                     // error : constexpr 함수 내 side effect 
}

constexpr int bad2(int a)
{
    if(a>=0) return a; 
    else return -a;               // error : constexpr 함수 내 if문
}

constexpr int bad3(int a)
{
    int sum = 0;                  // error : constexpr 함수 내 지역변수 
    for(int i=0; i<a; ++i)        // error : constexpr 함수 내 반복문
        sum += fac(i);
    return sum;
}

12.1.6.2. 조건부 평가

  • constexpr 함수 내에서 결과를 알 수 없는 조건 표현식은 컴파일 타임에 결과 값이 계산되지 않는다. -> 이는 해당 조건 표현식의 결과 값이 런타임에 계산된다는 뜻이다.

  • 다음 코드를 살펴보자.

    • low 와 high 는 컴파일 타임에 알 수 이는 환경 매개변수이며,

    • f(x,y,z) 는 구현에 따라서 결과 값이 달라진다는 것을 알 수 있다.

constexpr int low = 0;
constexpr int hign = 99;

constexpr int check(int i)
{
    return (low<=i && i<high) ? i : throw out_of_range("check() failed");
}

constexpr int val0 = check(50);         // OK
constexpr int val1 = check(f(x,y,z));   // 아마도 OK
constexpr int val2 = check(200);        // 예외를 던진다.

12.1.7 [[noreturn]] 함수

  • [[...]] 구조는 속성(attribute) 라고 불리며, c++ 문법 거의 어디에나 넣을 수 있다.

  • 표준 속성은 다음 두가지가 있다.

    • [[noreturn]] : 함수 선언 맨 앞에 두면 해당 함수의 반환을 기대하지 않는다는 의미이다.

    • [[carries_dependency]] :

  • 다음 코드를 살펴보자 -> [[noreturn]] 속성에도 불구하고, 함수가 반환한다면 무슨 일이 일어날지는 정의되어 있지 않다.

[[noreturn]] void exit(int);    // exit 는 절대로 반환하지 않는다. 

12.1.8 지역 변수

  • 어떤 함수 내에 정의된 변수를 지역 변수(local name) 이라고 부른다.

  • 지역 변수나 상수는 실행 스레드가 자신의 정의에 다다를 때 초기화된다.

  • 변수는 static 으로 선언돼 있지 않는 한 함수가 호출될 때마다 해당 변수의 복사본이 만들어진다. -> 만약 static 지역 변수가 있다면 실행 스레드가 자신의 정의의 다다를 때 딱 한번 초기화 될 것이다.

12.2 인자 전달

12.2.1 참조자 인자

  • 참조에 의한 호출 인자를 변경하는 함수는 프로그램을 읽기 어렵게 만들기 때문에, 대부분의 경우 피해야 한다.

  • 하지만 대규모 개체의 경우 값에 의한 전달보다 참조에 의한 전달이 눈에 띄게 효율적일 수 있다. -> 복사가 일어나지 않기 때문이다. (해당 경우는 참조자를 매개변수로 사용하는게 효율적이다)

  • 호출된 함수가 개체의 값을 변경하지 못하게 하기 위해 해당 인자를 const 로 선언할 수도 있다. -> const 선언은 프로그램의 규모가 커질수록 중요해진다. (안정적인 프로그램)

  • 리터럴, 상수 인자는 const T& 인자로 전달 될 수 있지만, T& 인자로는 전달될 수 없다. -> const T& 는 임시변수를 만들어준다.

12.2.2 배열 인자

  • 배열이 함수의 인자로 사용된다면 배열의 첫 번째 워소를 가리키는 포인터가 전달된다. -> 배열은 값에 의해 전달되는 것이 아닌, 포인터가 전달된다(일반 타입과 다른점)

  • 호출된 함수는 배열의 크기를 알 수 없다. (오류가 발생하는 주원인) -> 이를 방지하기 위해서 C 스타일 문자열은 '\0' 으로 종료된다는 점을 이용하거나, -> 또는, 함수에 배열의 크기를 알려주는 인자를 받는다. void compute1 (int* vec_ptr, int vec_size);

  • 위의 방법은 차선책이기 때문에, 대부분은 vector, array, map 과 같이 배열의 사이즈를 조절하는 컨테이너를 사용한다. (가장 좋은 방법)

  • 하지만 이 방법도 싫다면 아래 코드와 같이 함수의 인자로 배열에 참조자 타입을 선언할 수도 있다.

void f (int(&r) [4]);
void g()
{
    int a1[] = {1,2,3,4};
    int a2[] = {1,2};
    
    f(a1);    // ok
    f(a2);    // error : 원소의 개수가 틀리다. 
}

12.2.3 리스트 인자

  • 일반적으로아래처럼 리스트 인자가 사용되는 것을 볼 수 있다. -> 컴파일러는 함수가 해당 인자를 최대한 사용할 수 있도록 한다.

template<typename T>
void f1(initializer_list<T>);

struct S{
    int a;
    string s;
};
void f2(S);

template<typename T, int N>
void f3(T (&r)[N]);

void f4(int);

void g()
{
    f1({1,2,3,4});    // T 는 int 이고 initializer_list 는 size() 4를 갖는다. 
    f2({1,"MKS"});    // f2(S{1,"MKS"})
    f3({1,2,3,4});    // T 는 int 이고 N 은 4다.
    f4({1});          // f4(int{1});
}
  • 하지만, 함수 오버라이딩에 의해 애매한 상황이 발생한다면, initializer_list 매개변수가 우선권을 갖는다. -> 그 이유는, 리스트의 원소 개수를 기준으로 함수를 선택하게 된다면 상당히 혼동을 일으키기가 쉽다. -> 오버로딩에서 모든 형태의 혼동을 없애는 것은 불가능 하겠지만 initializer_list 매개변수가 우선권을 가지면, 대부분의 혼동 가능성을 최소화할 수 있다.

template<typename T>
void f(initailizer_list<T>);

struct S{
    int a;
    string s;
}
void f(S);

template<typename T, int N>
void f(T (&r)[N]);

void f(int);

void g()
{
    f({1,2,3,4});    // T는 int이고 initializer_list 는 size() 4를 갖는다.
    f({1, "MKS"});   // f(S) 를 호출한다.
    f({1});          // T는 int이고 initializer_list 는 size() 1를 갖는다.
}

12.2.4 인자의 개수가 지정되지 않은 경우

  • 어떤 함수에서 호출 내에 있어야 할 모든 인자의 개수와 타입을 지정할 수 없을 때 사용하는 방법은 다음 3가지 이다.

    • 가변 인자 템플릿 사용 (다른 챕터에서 설명)

    • initializer_list 인자 타입으로 사용 (다른 챕터에서 설명)

    • 매개변수를 ... 로 끝내는 것 (이번 챕터에서 설명) -> <cstdarg> 에 있는 일부 매크로를 사용해 임의의 타입으로 이뤄진 임의의 개수를 인자로 처리할 수 있다. -> 하지만, 이 방식은 타입 안정적이지 않으며, 복잡한 사용자 정의 타입과 함께 쓰이기는 어렵다.

  • 잘 설계된 프로그램이라면 인자 타입이 완벽하게 지정되지 않은 함수는 많아야 몇 개 내외여야 한다. -> 오버로딩 된 함수, 기본 인자를 사용하는 함수, initailizer_list 를 사용하는 함수, 가변 인자 템플릿을 사용하는 함수는 대부분의 상황에서 타입 체크를 수행하는데 사용 할 수 있다.

  • 오직 인자의 개수와 인자의 타입이 모두 가변적이거나, 가변 인자 템플릿의 사용이 바람직하지 않은 경우에만 ... 를 사용하는 케이스가 필요해진다.

12.3 오버로딩 함수

  • 대다수의 경우 다른 함수에는 다른 이름을 붙이는 것이 좋은 방법이지만, 서로 다른 함수가 개념적으로 같은 작업을 다른 타입의 매개변수에 대해서 수행할 때 그들에게 같은 이름을 붙이는 것이 더 편리할 수 있다.

12.3.1 자동 오버로딩 해결

  • 함수 fct 가 호출되면 컴파일러는 fct 란 이름의 함수 중 어떤 것을 실행할지 판단해야 한다.

  • 이 과정은 실제 인자의 타입을 유효 범위 내애 있는 fct 란 모든 이름의 함수 매개변수 타입과 비교함으로써 이루어진다.

  • 기본적인 방안은 인자와 가장 일치하는 함수를 실행하고, 이런 함수가 없다면 컴파일 오류를 내는 것이다.

void print(double);
void print(long);

void f()
{
    print(1L);
    print(1.0);
    print(1);    // compile error : 모호하다.
}
  • 함수 호출이 가능한지는 다음의 순서대로 판단한다. -> 다음 중 두 개의 일치가 발견되면 호출은 모호하다고 판단되어 컴파일 오류를 발생시킨다.

    • 정확한 일치

    • 타입 승격을 사용한 일치 (bool -> int, char -> int, short -> int ...)

    • 표준 변환을 사용한 일치 (int -> double, double -> int, double -> long double ...)

    • 사용자 정의 타입 변환을 사용한 일치(double -> complex<double>)

    • 함수 선언에서 ... 을 사용한 일치

12.3.2 오버로딩과 반환 타입

  • 오버로딩은 매개변수만 고려한다.

  • 반환타입은 고려하지 않는다.

float sqrt(float);
double sqrt(double);

void g(double da, float fla)
{
    float f1 = sqrt(da);
    double d = sqrt(da);
    f1 = sqrt(fla);
    d = sqrt(fla);
}

12.3.5 수동 오버로딩 해결

  • 함수의 오버로딩을 너무 적게 or 많이 선언하면 모호한 상황을 일으킬 수 있다.

void f1(char);
void f1(long);

void f2(char*);
void f2(int*);

void k(int i)
{
    f1(i);    // 모호하다. f1(char), f1(long) 중 어느쪽?
    f2(0);    // 모호하다. f2(char*), f2(int*) 중 어느쪽?
}
  • 다음과 같은 방법들을 통해 위상황을 해결할 수 있다. -> 하지만 미봉책이지, 상황에 따라서 수동으로 해결해주어야 한다.

    • inline void f1(int n) {f1(long(n)); }

    • f2(static_cast<int*>(0));

12.5 함수를 가리키는 포인터

  • 개체와 마찬가지로 함수 본체를 위해 생성된 코드는 메모리 어딘가에 위치하므로, 그에 따른 주소를 갖는다. -> 개체를 가리키는 포인터를 가질 수 있는 것처럼 함수를 가리키는 포인터를 가질 수 있다.

  • 하지만 여러가지 이유로 함수를 가리키는 포인터는 코드의 변경을 허용하지 않는다. -> 컴퓨터 아키텍처, 시스템 설계와 관련되어 있다.

  • 함수를 통해 우리가 할 수 있는 것은 딱 두가지이다.

    • 함수를 호출하던가,

    • 그것의 주소를 얻는 것이다.

  • 함수의 주소를 통해 얻어진 포인터는 해당 함수를 호출하는 데 사용할 수 있다.

    • 컴파일러는 efct 가 포인터라는 점을 알아내고 그것을 가리키는 함수를 호출할 것이다. -> efct("error"); == error("error");

void error (string s); { ... }
void (*efct) (string) ;    // 문자열 인자를 받아들이고, 아무것도 반환하지 않는 함수를 가리키는 포인터
void f() 
{
    efct = &error;    // efct 는 error 를 가리킨다. 
    efct("error");   // efct 를 통해 error 를 호출한다. 
}
  • 위 코드와 마찬가지로 & 를 이용해서 함수의 주소를 알아내는 것 역시 선택적이다.

void (*f1)(string) = &error;    // ok : error 와 같다. 
void (*f2)(string) = error;     // ok : &error 와 같다. 
void g()
{
    f1("Vasa");            // ok : (*f1)("Vasa") 와 같다. 
    f2("Mary Rose");    // ok : f1("Mary Rose"); 와 같다. 
}
  • 함수를 가리키는 포인터는 함수 자체와 완전히 똑같이 선언된 인자 타입을 갖는다. -> 포인터 대입에서는 전체 함수 리턴타입과 매개변수가 일치해야 한다.

void (*pf)(string);        // void(string)를 가리키는 포인터
void f1(string);           // void(string)
int f2(string);            // int(string)
void f3(int*);             // void(int*)

void f()
{
    pf = &f1;    // ok
    pf = &f2;    // error : 리턴타입 불일치 
    pf = &f3;    // error : 리턴타입 불일치 
    
    pf("Hera");            // ok
    pf(1);                 // error : 인자타입 불일치
    int i = pf("Zeus");    // error : void 타입이 int 타입에 대입될 수 없다. 
}

Previous11. 선택 연산Next13. 예외 처리

Last updated 1 year ago