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
  • 11.2 힙 영역 메모리
  • 11.2.1 메모리 관리
  • 11.2.2 배열
  • 11.2.3 메모리 공간 확보
  • 11.3 리스트 {}
  • 11.3.1. 리스트 {} 구현 모델
  • 11.3.2 qualified list
  • 11.3.3 unqualified list
  • 11.4 람다 표현식
  • 11.4.1 구현 모델
  • 11.4.2 람다의 대안
  • 11.4.3 캡처
  1. Language
  2. C++
  3. 책
  4. The C++ Programming Lanuaage
  5. 2부 : 기본 기능

11. 선택 연산

11.2 힙 영역 메모리

11.2.1 메모리 관리

  • 힙 저장 공간의 주요 문제점은 다음과 같다.

    • 메모리 누수(leaked object) : new 를 통해 힙 영역에 데이터를 생성하고 나서 delete 를 사용하지 않을 때 메모리 누수가 발생한다. -> 메모리 누수는 프로그램의 저장 공간을 줄이기 때문에, 심각한 문제가 될 가능성이 있다.

    • 빠른 소멸(premature deletion) : 개체를 delete 하고 나중에 그 포인터를 사용하고자 할 때

    • 중복 소멸(double deletion) : 개체를 두번 소멸할 때 -> 한번 개체를 소멸하면, 해당 메모리 공간은 운영체제에 의해 다른 변수에게 할당될 수 있다. -> 두번 개체를 소멸하는 것은 다른 변수의 메모리 공간을 소멸한다는 것! (정말 잘못된 상황을 초래할 수 있다.. )

  • 위와 같은 문제점이 있기에, 'new', 'delete' 를 사용하는 방식에 두 가지 권장 사항이 있다.

    • 꼭 필요하지 않다면, 동적 할당을 하지 않는다. -> 스택에 변수를 우선적으로 저장한다.

    • 힙 영역에 개체를 생성할 때, 그것들을 관리해주는 표준 라이브러리 or 컨테이너를 사용해라. -> 예를 들어, STL 컨테이너는 기본적으로 동적으로 메모리 공간을 관리해주고, 내부적으로 개체의 생성과 소멸을 담당한다. -> string, vector, unique_ptr, shared_ptr 등 ..

11.2.2 배열

  • 개체의 배열 역시 new 로 생성할 수 있다.

  • 개체의 배열을 삭제할 때 delete[] 가 사용된다. -> [] 를 사용하지 않는다면 배열의 첫번째 메모리만 해제되고, 나머지 해제되지 않는다.

  • 배열과 컨테이너는 new 를 통해 힙영역에 생성할 수 있지만, 지역 개체에는 new 를 사용하지 말아야 한다. -> 아래 코드는 비효율적이다.. -> return 이나 delete 이전에 발생한 예외는 메모리 누수를 일으킬 것이다. (지역 변수를 사용하자.. )

// 사용하면 안되는 방식!
void f1()
{
    X* p = new X;
    // ... *p 를 사용한다. 
    
    delete p;
}

// 사용해도 되는 방식.
void f2() 
{
    X x; 
    // ... x 를 사용한다. 
}

11.2.3 메모리 공간 확보

  • new 키워드를 통해 힙 영역의 메모리 공간을 확보할 수 있다.

  • new 가 할당할 수 있는 공간을 찾지 못하면 기본 설정 상 std::bad_alloc 이라는 예외를 던진다. -> 아래 코드는 엄청난 메모리 공간을 소모하고, 결국 bad_alloc 예외를 호출할 수 밖에 없다.

void f()
{
    vector<char*> v;
    try{
        for(;;){
            char* p = new char[10000];    // 메모리를 확보한다.
            v.push_back(p);               // 새로운 메모리가 사용될 공간을 체크한다. 
            p[0] = 'x';                   // 새로운 메모리를 사용한다. 
        }
    }
    catch(bad_alloc){
        cerr << "Memory schausted!\n";
    }
}

11.3 리스트 {}

11.3.1. 리스트 {} 구현 모델

{} 는 아래와 같이 다양한 용도로 사용된다.

-> 이는 {} 가 범용 모델이며 컴파일러에 의해 효율적인 최적화가 가능하다.

  1. {} 가 생성자로 사용되는 경우 -> A a{1, 2}; 의 형태로 사용되는데, 해당 형태는 데이터 손실이 있는(Narrowing) 변환을 불어한다. -> 또한 initializer_list 를 매개변수로 받는 생성자가 오버로딩 되어 있다면 컴파일러는 가장 먼저 해당 생성자를 사용하기 때문에, 사용에 주의가 필요하다.

  2. {} 가 어떤 집합체(배열, 컨테이너, 클래스 등.. ) 의 원소를 초기화하는 경우

  3. {} 가 initializer_list 개체를 생성하는데 사용되는 경우

{} 타입은 auto 와 함께 사용시 모든 원소가 동일한 타입일 경우에만 사용이 가능하다.

auto x0 = {};        // error : 원소 타입이 없음    
auto x1 = {1};       // initializer_list<int>
auto x2 = {1,2};     // initializer_list<int>
auto x3 = {1,2,3};   // initializer_list<int>
auto x4 = {1,2.0};   // error : 동일한 타입의 원소가 아니다. 

11.3.2 qualified list

  • 특정 클래스나 구조체의 멤버를 초기화 할 때 {} 사용하면 qualified list 라고 부른다.

struct s {int a,b;};
void f()
{
    s v {1,2};
    v = s{1,2};
    s* p = new s{1,2}
}

11.3.3 unqualified list

  • 특정 클래스나 구조체의 멤버를 초기화하는 용도가 아닌 경우 unqualified list 라고 부른다.

일반적인 템플릿 인자에 대해 unqualified list 타입을 추론할 수 없다.

  • auto 타입은 {} 를 initializer_list<> 타입으로 추론해 주었지만, 템플릿에서는 명시적으로 타입을 지정해주지 않는다면 컴파일 에러가 발생한다. -> 이는 언어상의 제약이다!

template<typename T>
void f(T);

f({});        // error : 초기화 식의 타입을 모른다. 
f({1});       // error : qualified list 가 일반적인 T 와 일치하지 않는다. 
f({1,2});     // error : qualified list 가 일반적인 T 와 일치하지 않는다. 
f({1,2,3});   // error : qualified list 가 일반적인 T 와 일치하지 않는다. 

f<int>({})         // success
f<int>({1});       // success
f<int>({1,2});     // 
f<int>({1,2,3});   // success
  • 마찬가지로 아래의 경우에도 컴파일러는 타입을 추론하지 못해 컴파일 에러를 발생시킨다. -> f2 함수 사용 어디에도 vector 를 사용한다는 걸 알 수 없다.. (추론 불가) -> 너무 모호한 정보만을 제공하고 추론해달라고 하는 것은 약간 무책임? 하다 생각된다..

template<typename T>
void f2(const vector<T>&);
f2({1,2,3});          // error
f2({"1","2","3"});    // error

11.4 람다 표현식

  • 람다는 이름이 없는 함수 개체를 정의하고, 사용하기 위한 목적의 단순화된 표기법이다.

  • 이런 사용 방식은 일반적인 함수 사용 방식보다는, 특히 연산을 인자로 알고리즘에 전달하고자 할 때 유용하다!

  • 람다 표현식은 몇가지 부분으로 나뉜다.

    • 캡처 리스트 : 캡부 변수들을 복사 or 참조할 것인지를 표현

    • 매개변수 리스트 : 람다 표현식에 어떤 인자가 필요한지 표현

    • mutable 지정자 : 값에 의해 갭처된 변수의 대한 람다의 복제본을 변경 하도록 표현

    • -> 타입 : 반환타입을 지정

    • 람다 본체 {} : 실행될 코드를 지정

11.4.1 구현 모델

실제로 람다식은 컴파일러가 해당 람다식에 대한 익명 클래스를 생성하고, 해당 클래스의 개체를 만들어서 사용한다!

  • 람다로 구현한 다음 코드를 살펴보자

[&os, m](int x) {if(x%m == 0) os << x << '\n';}
  • 위 코드의 의미를 살피기 위해서 컴파일러가 만들어낸, 동일한 역할을 하는 함수가 있는 개체를 정의해보자.

    • 캡처 리스트는 두 개의 멤버 변수와 그것을 초기화하는 하나의 생성자가 된다. (복사생성자)

    • 람다의 본체는 단순히 operator() 의 본체가 된다.

    • 위 람다식은 값을 반환하지 않기 떄문에, 리턴타입은 void 이다.

    • 기본 설정상 operator() 는 const 이므로, 람다 본체는 캡처된 변수를 변경하지 않는다. -> 람다 본체에서 캡처된 변수를 변경하고 싶다면 mutable 로 선언해야 한다.

    • 아래와 같이 람다로부터 생성되는 임시 클래스 개체를 closure object 라고 부른다.

class Modulo_print {
    ostream& os;
    int m;
public:
    Modulo_print (ostream& s, int mm) : os(s), m(mm) {}     
    void operator()(int x) const
    {
         if(x%m == 0) os << x << '\n';
    }
}

클로저 개체(closure object)

  • 람다 표현식이 사용될 때 컴파일러는 클로저 객체라는 특별한 타입의 객체를 생성합니다.

  • 클로저 개체는 다음과 같은 특징을 가진다.

    • 캡처된 변수들

      • 람다 표현식의 캡처 목록에 있는 변수들을 멤버 변수로 저장합니다.

      • 캡처 방법에 따라 값으로 캡처하거나 참조로 캡처할 수 있습니다.

    • 연산자 오버로딩

      • 람다 표현식의 함수 본문은 클로저 객체의 operator()로 구현됩니다.

  • 아래의 람다식을 컴파일러가 클로저 개체로 만들어 준다면 대략적으로 다음과 같을 것이다.

#include <iostream>
#include <functional>

int main() {
    int x = 10;
    int y = 20;

    // 값으로 캡처
    auto lambda1 = [x, y]() {
        return x + y;
    };

    // 참조로 캡처
    auto lambda2 = [&x, &y]() {
        x += 1;
        y += 1;
        return x + y;
    };

    std::cout << "lambda1: " << lambda1() << std::endl; // 30
    std::cout << "lambda2: " << lambda2() << std::endl; // 32
    std::cout << "x: " << x << ", y: " << y << std::endl; // 11, 21

    return 0;
}
class __lambda_unnamed {
public:
    __lambda_unnamed(int captured_x, int captured_y)
        : x(captured_x), y(captured_y) {}

    int operator()() const {
        return x + y;
    }

private:
    int x;
    int y;
};
  • 의미를 파악했으니 실제 코드를 사용할 때 아래와 같이 사용할 수 있다. -> 재사용 할 것 같지 않는 코드인데, 내부적으로 클래스를 정의하는 것은 생각보다 번잡한 일이다..

void print_modulo(const vector<int>& v, ostream& os, int m)
{
    for_each(begin(v), end(v), [&os, m](int x) {if(x%m == 0) os << x << '\n';} );
}

void print_modulo(const vector<int>& v, ostream& os, int m)
{
    for_each(begin(v), end(v), Modulo_print{os, m} );
}

11.4.2 람다의 대안

  • 람다를 사용하기 이전에 케이스부터 람다를 사용하고 또 개선하는 케이스를 살펴보자.

step1. 람다식 대신 단 한번만 사용되는 직전 클래스 생성

void print_modulo(const vector<int>& v, ostream& os, int m)
{
    class Modulo_print {
        ostream& os;
        int m;
    public:
        Modulo_print (ostream& s, int mm) : os(s), m(mm) {}     
        void operator()(int x) const
        {
             if(x%m == 0) os << x << '\n';
        }
    }
    for_each(begin(v), end(v), Modulo_print{os, m} );
}

step2. 람다식 사용

void print_modulo(const vector<int>& v, ostream& os, int m)
{
    // 람다식에 이름을 붙일수도 있다!
    auto Modulo_print = [&os, m](int x) {if(x%m == 0) os << x << '\n';};
    for_each(begin(v), end(v), Modulo_print);
}

step3. for_each 대신 for 문사용(람다식 미사용)

-> 더욱 직관적이다.

void print_modulo(const vector<int>& v, ostream& os, int m)
{
    for(auto x : v)
        if(x%m == 0)
            os << x << '\n';
}

step4. 좀 더 많은 컨테이너를 사용하기 위한 템플릿 사용

template<typename C, typename Fct
void print_modulo(const vector<int>& v, ostream& os, int m, Fct f)
{
    for(auto x : v)
        if(f(x)%m == 0)
            os << x << '\n';
}

void test(vector<int>& v, map<string, int>& m)
{
    print_modulo(v, cout, 99, [](int x){return x;});
    print_modulo(m, cout, 77, [](const pair<const string, int>& x){return x.second;});
}

11.4.3 캡처

11.4.3.1 람다와 수명

  • 람다는 참조를 캡처할 때, 호출자보다 오래 살아남을 수 있다. -> 이 때문에, 문제가 생기게 된다.

  • 아래 코드가 문제가 생기는 과정은 다음과 같다.

    • setup() 이 종료되고 차후에(아마 몇분 뒤) 사용자가 "draw triangle" 버튼을 누르면 람다는 오래 전에 사라진 지역 변수에 접근하려 할 것이고,

    • 이는 지역변수가 이미 사라진 후이기 때문에, 문제가 발생한다.

void setup(Menu& m)
{
   // ... 
   Point p1, p2, p3;
   // ... p1, p2, p3 의 위치를 계산
   m.add("draw triangle", [&] {m.draw(p1, p2, p3);});   // 문제가 터질 가능성
}
  • 때문에, 람다가 호출자보다 오래 살아 남을것이라고 생각한다면 값들이 return 매커니즘이나, 적절한 인자를 통해서 반환 되도록 만들어야 한다. -> = 캡처 리스트는 람다가 만들어지는 순간 값을 복사해놓기 때문에, 이후에 사용하더라도 문제가 없다.

void setup(Menu& m)
{
   // ... 
   Point p1, p2, p3;
   // ... p1, p2, p3 의 위치를 계산
   m.add("draw triangle", [=] {m.draw(p1, p2, p3);});   
}

11.4.3.3 람다와 this

  • 멤버 함수에 쓰인 람다에서 클래스 개체의 멤버에 접근하기 위해서 캡처 리스트에 this 를 추가하면 된다. -> [this] 은 값에 의한 캡처가 아닌, 참조에 의한 캡처이다. -> 때문에, 주의가 필요하다..

class Request
{
    function<map<string, string>(const map<string, string>&)> oper;    // 연산
    map<string, string> values;    // 인자    
    map<string, string> results;    // 결과
public:
    Request<const string& s);
    future<void> execute()    // 비동기적으로 실행
    {
        return async([this](){results=oper(value);});
    }
}

11.4.3.4 mutable 람다

  • 대부분의 함수 개체(closure Object) 의 상태를 변경할 필요가 없기 때문에, 기본 설정상 값에 의해 캡처된 값에 대한 변경은 불가능하게 되어있다. -> operator() 는 const 이다.

  • 상태를 변경해야 하는 이벤트가 만약에 생긴다면 mutable 로 선언할 수 있다.

void algo(vector<int>& v)
{
    int count = v.size();
    std::generate(v.begin(), v.end(),
                [count]() mutable { return --count; }
    );
}    

Previous10. 표현식Next12. 함수

Last updated 1 year ago