본문 바로가기

함수 객체

호출 연산자와 상태를 가진 함수 객체를 만들고 표준 함수 객체·람다를 STL 알고리즘의 동작으로 전달합니다.

알고리즘들의 유연성을 더욱 높여주고, 사용자 정의 동작을 주입할 수 있게 해주는 것이 바로 함수 객체(Function Objects), 또는 줄여서 함수자(Functors)입니다.

이번 장에서는 함수 객체가 무엇인지, 왜 필요한지, 그리고 STL 알고리즘과 함께 어떻게 활용되는지 정리합니다.

또한 C++11 이후 도입된 람다(Lambda) 표현식이 함수 객체와 어떤 관계를 가지는지도 함께 다룹니다. 앞 절의 for_each·transform과 클래스 멤버 상태를 바탕으로 읽습니다. 아래 한글 자리 표시 형식은 실행 코드가 아니고, 각 main이 있는 예제는 별도 파일 하나로 실행합니다.

알고리즘에 넣을 callable은 상태와 보관 계약으로 고른다

순차 for_each의 Sum 복사본이 1→3→6으로 누적되고 initial은 0으로 남는 새 본문 실험과 연결되어 복사본 상태 변화가 핵심이다.

C++ · CALLABLE CHOICE

알고리즘은 순회하고 callable은 동작을 정의한다

상태·재사용·수명·저장 경계를 기준으로 호출 가능한 표현을 고른다. 함수 객체와 lambda는 본문의 예시이며, 일반 함수와 std::function은 선택 기준을 넓힌 비교이다.

순차 for_each의 복사된 함수 객체 상태와 원래 객체를 구분한다새본문Sum예제5노드4간선. 초기Sum0→값매개변수로복사된Sum0→원소1,2,3의순차호출로상태1,3,6→반환객체6. 초기객체에서별도간선은그대로0인원객체로향한다. original이6으로바뀌는간선이나반환값이원본상태를다시덮는간선은없다. executionpolicy없는해당for_each예제에한정하며기존4호출가능타입비교는보조설명으로그대로보존한다.원래 Sum 객체initial.total = 0값 매개변수의 복사순차 for_each에 전달복사본 total = 0각 원소로 호출1 → 2 → 3total: 1 → 3 → 6반환받은 객체result.total = 6원래 객체는 유지initial.total = 0별도 객체execution policy 없는 예제모든 알고리즘의 보편 규칙 아님
순차 for_each의 복사된 함수 객체 상태와 원래 객체를 구분한다새본문Sum예제5노드4간선. 초기Sum0→값매개변수로복사된Sum0→원소1,2,3의순차호출로상태1,3,6→반환객체6. 초기객체에서별도간선은그대로0인원객체로향한다. original이6으로바뀌는간선이나반환값이원본상태를다시덮는간선은없다. executionpolicy없는해당for_each예제에한정하며기존4호출가능타입비교는보조설명으로그대로보존한다.원래 Sum 객체initial.total = 0값 매개변수의 복사순차 for_each에 전달복사본 total = 0각 원소로 호출1 → 2 → 3total: 1 → 3 → 6반환받은 객체result.total = 6원래 객체는 유지initial.total = 0별도 객체execution policy 없는 예제모든 알고리즘의 보편 규칙 아님

일반 함수 · 상태 없는 재사용

이름 있는 함수를 여러 알고리즘에 전달한다.

void printNumber(int n);

함수 자체는 인스턴스별 멤버 상태를 갖지 않는다.

함수 객체 · 이름 있는 상태

operator()와 멤버로 정책과 상태를 묶는다.

AddAndPrint(10)

반복되는 정책에 이름을 주고 상태를 보관한다.

lambda · 호출 지점의 지역 동작

알고리즘 옆에 짧은 동작을 적고 필요하면 값을 캡처한다.

[valueToAdd](int n) {
  std::cout << n + valueToAdd;
}

참조 캡처는 참조 대상의 수명을 확인한다.

std::function · 공통 저장 경계

서로 다른 복사 가능한 callable을 같은 함수 서명으로 보관한다.

std::function<void(int)>

type erasure가 필요한 저장 경계에서 비용을 검토한다.

즉시 호출에는 구체 타입을 유지할 수 있다. 참조 캡처나 보관은 callable과 참조 대상의 수명을 함께 확인한다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
원 도표의 제목·노드·관계·조건·주석
범위원문
관계·조건 설명새 본문의 Sum 예제는 5개 노드와 4개 간선으로 이루어진다. 초기 Sum 0→값 매개변수로 복사된 Sum 0→원소 1,2,3의 순차 호출로 상태 1,3,6→반환 객체 6으로 이어진다. 초기 객체에서 별도 간선은 그대로 0인 원래 객체로 향한다. original이 6으로 바뀌는 간선이나 반환값이 원본 상태를 다시 덮는 간선은 없다. execution policy가 없는 해당 for_each 예제에 한정하며 기존 4가지 호출 가능 타입 비교는 보조 설명으로 그대로 보존한다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
함수 객체 복사

initial의 복사본으로 순차 for_each를 시작한다.

  • initial.total: 0
  • 작업 복사본.total: 0
  • 알고리즘은 값으로 전달받은 함수 객체에 상태를 누적한다.
호출 누적

복사본이 1·2·3을 차례로 처리한다.

  • 작업 복사본.total: 1 → 3 → 6
  • initial.total: 0
  • 각 호출은 해당 복사본의 total에 입력 값을 더한다.
결과 객체 반환

알고리즘의 반환값을 result로 받는다.

  • result.total: 6
  • initial.total: 0
  • 반환되는 것은 누적한 객체이며 원래 initial을 참조로 바꾼 것이 아니다.

알고리즘에 넣을 callable은 상태와 보관 계약으로 고른다

순차 for_each의 Sum 복사본이 1→3→6으로 누적되고 initial은 0으로 남는 새 본문 실험과 연결되어 복사본 상태 변화가 핵심이다.

C++ · CALLABLE CHOICE

알고리즘은 순회하고 callable은 동작을 정의한다

상태·재사용·수명·저장 경계를 기준으로 호출 가능한 표현을 고른다. 함수 객체와 lambda는 본문의 예시이며, 일반 함수와 std::function은 선택 기준을 넓힌 비교이다.

순차 for_each의 복사된 함수 객체 상태와 원래 객체를 구분한다새본문Sum예제5노드4간선. 초기Sum0→값매개변수로복사된Sum0→원소1,2,3의순차호출로상태1,3,6→반환객체6. 초기객체에서별도간선은그대로0인원객체로향한다. original이6으로바뀌는간선이나반환값이원본상태를다시덮는간선은없다. executionpolicy없는해당for_each예제에한정하며기존4호출가능타입비교는보조설명으로그대로보존한다.원래 Sum 객체initial.total = 0값 매개변수의 복사순차 for_each에 전달복사본 total = 0각 원소로 호출1 → 2 → 3total: 1 → 3 → 6반환받은 객체result.total = 6원래 객체는 유지initial.total = 0별도 객체execution policy 없는 예제모든 알고리즘의 보편 규칙 아님
순차 for_each의 복사된 함수 객체 상태와 원래 객체를 구분한다새본문Sum예제5노드4간선. 초기Sum0→값매개변수로복사된Sum0→원소1,2,3의순차호출로상태1,3,6→반환객체6. 초기객체에서별도간선은그대로0인원객체로향한다. original이6으로바뀌는간선이나반환값이원본상태를다시덮는간선은없다. executionpolicy없는해당for_each예제에한정하며기존4호출가능타입비교는보조설명으로그대로보존한다.원래 Sum 객체initial.total = 0값 매개변수의 복사순차 for_each에 전달복사본 total = 0각 원소로 호출1 → 2 → 3total: 1 → 3 → 6반환받은 객체result.total = 6원래 객체는 유지initial.total = 0별도 객체execution policy 없는 예제모든 알고리즘의 보편 규칙 아님

일반 함수 · 상태 없는 재사용

이름 있는 함수를 여러 알고리즘에 전달한다.

void printNumber(int n);

함수 자체는 인스턴스별 멤버 상태를 갖지 않는다.

함수 객체 · 이름 있는 상태

operator()와 멤버로 정책과 상태를 묶는다.

AddAndPrint(10)

반복되는 정책에 이름을 주고 상태를 보관한다.

lambda · 호출 지점의 지역 동작

알고리즘 옆에 짧은 동작을 적고 필요하면 값을 캡처한다.

[valueToAdd](int n) {
  std::cout << n + valueToAdd;
}

참조 캡처는 참조 대상의 수명을 확인한다.

std::function · 공통 저장 경계

서로 다른 복사 가능한 callable을 같은 함수 서명으로 보관한다.

std::function<void(int)>

type erasure가 필요한 저장 경계에서 비용을 검토한다.

즉시 호출에는 구체 타입을 유지할 수 있다. 참조 캡처나 보관은 callable과 참조 대상의 수명을 함께 확인한다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.

관계도의 보충 설명

원 도표의 제목·노드·관계·조건·주석
범위원문
관계·조건 설명새 본문의 Sum 예제는 5개 노드와 4개 간선으로 이루어진다. 초기 Sum 0→값 매개변수로 복사된 Sum 0→원소 1,2,3의 순차 호출로 상태 1,3,6→반환 객체 6으로 이어진다. 초기 객체에서 별도 간선은 그대로 0인 원래 객체로 향한다. original이 6으로 바뀌는 간선이나 반환값이 원본 상태를 다시 덮는 간선은 없다. execution policy가 없는 해당 for_each 예제에 한정하며 기존 4가지 호출 가능 타입 비교는 보조 설명으로 그대로 보존한다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
함수 객체 복사

initial의 복사본으로 순차 for_each를 시작한다.

  • initial.total: 0
  • 작업 복사본.total: 0
  • 알고리즘은 값으로 전달받은 함수 객체에 상태를 누적한다.
호출 누적

복사본이 1·2·3을 차례로 처리한다.

  • 작업 복사본.total: 1 → 3 → 6
  • initial.total: 0
  • 각 호출은 해당 복사본의 total에 입력 값을 더한다.
결과 객체 반환

알고리즘의 반환값을 result로 받는다.

  • result.total: 6
  • initial.total: 0
  • 반환되는 것은 누적한 객체이며 원래 initial을 참조로 바꾼 것이 아니다.

그림의 std::function은 서로 다른 호출 객체를 같은 타입으로 보관할 때의 확장 선택지입니다. 아래 예제는 구체 함수 객체와 람다를 알고리즘에 직접 전달합니다.


함수 객체(Function Objects)란 무엇인가?

함수 객체는 호출 연산자, 내부 상태, 알고리즘 전달 방식, 람다와의 차이를 기준으로 읽습니다.

함수 객체는 operator() 연산자를 오버로딩하여 함수처럼 호출될 수 있는 객체를 의미합니다.

즉, 객체이면서 동시에 함수처럼 동작하는 개체입니다.

기본 아이디어: 일반 함수나 함수 포인터를 STL 알고리즘에 전달할 수 있지만, 여기에는 몇 가지 한계가 있습니다.

상태 저장 불가: 일반 함수나 함수 포인터는 상태(멤버 변수)를 가질 수 없습니다.

특정 값을 기억하여 연산에 활용해야 하는 경우, 전역 변수를 사용하거나 클로저를 구현해야 하는 등 번거로워집니다.

타입 정보 부족: 함수 포인터 타입에도 매개변수·반환 타입 정보가 있습니다. 다만 호출 대상이 실행 시 결정되면 인라인화가 어려울 수 있으며, 컴파일러가 대상을 알면 최적화할 수도 있습니다.

함수 객체는 이러한 한계를 극복합니다.

함수 객체는 클래스의 인스턴스이므로 멤버 변수를 가질 수 있어 상태를 유지할 수 있습니다.

또한 함수처럼 호출될 때 operator() 연산자가 호출되므로 일반 함수와 동일한 방식으로 사용할 수 있습니다.

함수 객체 기본 형식
class MyFunctor {
public:
    // operator() 연산자 오버로딩
    반환타입 operator()(매개변수) const {
        // 함수처럼 동작할 코드
        return 결과;
    }
    // 멤버 변수를 통해 상태 저장 가능
    // int someState;
};

// 사용 예시:
MyFunctor obj;
obj(인자); // obj.operator()(인자)와 동일하게 호출

함수 객체의 필요성 및 장점

함수 객체는 주로 STL 알고리즘에 사용자 정의 동작을 주입할 때 그 진가를 발휘합니다.

장점

상태 유지: 멤버 변수를 통해 상태를 저장하고 연산에 활용할 수 있습니다.

타입 안전성 및 최적화: 템플릿 인자로 전달될 때 컴파일러가 해당 타입을 명확히 알 수 있어 더 나은 최적화를 기대할 수 있습니다. (함수 포인터는 런타임에 주소를 찾는 오버헤드가 있을 수 있습니다.)

유연성: 다양한 방식으로 알고리즘의 동작을 커스터마이징할 수 있습니다.

강력함: 람다 표현식의 등장으로 함수 객체 작성이 더욱 간편해졌습니다.

컨테이너의 모든 요소에 특정 값을 더해서 출력하는 함수 객체를 만들어 봅시다.

std::for_each와 함수 객체 사용 예시
#include <iostream>
#include <vector>
#include <algorithm> // for_each

// 요소를 더하고 출력하는 함수 객체
class AddAndPrint {
private:
    int _valueToAdd; // 상태 (멤버 변수)

public:
    // 생성자를 통해 초기 상태를 설정
    AddAndPrint(int val) : _valueToAdd(val) {}

    // operator() 오버로딩: 각 요소에 대해 호출될 함수처럼 동작
    void operator()(int n) const { // const를 붙여서 객체의 상태를 변경하지 않음을 명시
        std::cout << n + _valueToAdd << " ";
    }
};

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    std::cout << "Original numbers: ";
    for (int n : numbers) {
        std::cout << n << " ";
    }
    std::cout << std::endl;

    std::cout << "Adding 10 and printing: ";
    // AddAndPrint 객체를 생성하여 for_each에 전달
    std::for_each(numbers.begin(), numbers.end(), AddAndPrint(10));
    std::cout << std::endl; // 출력: 11 12 13 14 15

    std::cout << "Adding 100 and printing: ";
    std::for_each(numbers.begin(), numbers.end(), AddAndPrint(100));
    std::cout << std::endl; // 출력: 101 102 103 104 105

    return 0;
}

AddAndPrint 클래스는 _valueToAdd라는 멤버 변수를 가지고 있어 std::for_each가 각 요소를 처리할 때마다 이 값을 활용할 수 있습니다.

이는 일반 함수로는 직접적으로 하기 어려운 작업입니다. 원 AddAndPrint는 int n을 값으로 받아 출력할 뿐 numbers를 변경하지 않습니다. _valueToAdd는 함수 객체의 설정이고, 출력은 원소 값에 그 설정을 더한 결과입니다. 모든 함수를 함수 객체로 바꾸어야 하는 것은 아니며, 독립적으로 설정을 보관할 객체가 필요한지를 확인합니다.


STL이 제공하는 표준 함수 객체

STL은 <functional> 헤더에 미리 정의된 다양한 표준 함수 객체들을 제공합니다.

이들은 주로 연산자(+, -, *, /, <, >, == 등)를 캡슐화한 형태입니다.

산술 연산 함수 객체
  • std::plus<T>: a + b
  • std::minus<T>: a - b
  • std::multiplies<T>: a * b
  • std::divides<T>: a / b
  • std::negate<T>: -a
비교 연산 함수 객체
  • std::equal_to<T>: a == b
  • std::not_equal_to<T>: a != b
  • std::greater<T>: a > b
  • std::less<T>: a < b
  • std::greater_equal<T>: a >= b
  • std::less_equal<T>: a <= b
논리 연산 함수 객체
  • std::logical_and<T>: a && b
  • std::logical_or<T>: a || b
  • std::logical_not<T>: !a
표준 함수 객체 사용 예시
#include <iostream>
#include <vector>
#include <algorithm> // sort, transform
#include <functional> // std::greater, std::multiplies, std::plus

int main() {
    std::vector<int> numbers = {5, 1, 4, 2, 8};

    // std::sort와 std::greater를 사용하여 내림차순 정렬
    std::sort(numbers.begin(), numbers.end(), std::greater<int>());
    std::cout << "Sorted descending: ";
    for (int n : numbers) {
        std::cout << n << " ";
    }
    std::cout << std::endl; // 출력: 8 5 4 2 1

    std::vector<int> numbers2 = {1, 2, 3, 4, 5};
    std::vector<int> result_mult(numbers2.size());
    std::vector<int> result_plus(numbers2.size());

    // std::transform과 std::multiplies를 사용하여 각 요소에 2를 곱하기
    // 단항 transform에 bind가 만든 함수 객체를 전달하며 상수 2는 그 객체에 저장됩니다.
    std::transform(numbers2.begin(), numbers2.end(), result_mult.begin(), std::bind(std::multiplies<int>(), std::placeholders::_1, 2));
    // C++11 이전에는 std::bind1st, std::bind2nd 사용
    // C++11 이후에는 람다가 훨씬 간편

    std::cout << "Multiplied by 2: ";
    for (int n : result_mult) {
        std::cout << n << " ";
    }
    std::cout << std::endl; // 출력: 2 4 6 8 10

    // 두 벡터의 요소 더하기 (std::plus 사용)
    std::vector<int> v_a = {1, 2, 3};
    std::vector<int> v_b = {4, 5, 6};
    std::vector<int> v_sum(v_a.size());

    std::transform(v_a.begin(), v_a.end(), v_b.begin(), v_sum.begin(), std::plus<int>());
    std::cout << "Vector sum: ";
    for (int n : v_sum) {
        std::cout << n << " ";
    }
    std::cout << std::endl; // 출력: 5 7 9

    return 0;
}

람다 표현식 (C++11 이후)

C++11부터 도입된 람다 표현식은 함수 객체를 훨씬 간결하게 만들 수 있는 문법적 설탕(Syntactic Sugar)입니다.

람다는 익명(anonymous) 함수 객체를 즉석에서 생성하는 방법이며, 특히 알고리즘의 세 번째 인자로 작은 함수 객체를 전달할 때 매우 유용합니다.

람다 표현식 기본 형식
[캡처](매개변수) -> 반환타입 {
    // 함수 본문
}
  • [캡처]: 람다 외부의 변수를 람다 내부로 가져오는 방법을 지정합니다.
    • []: 아무것도 캡처하지 않음
    • [var]: var 변수를 값으로 캡처 (복사)
    • [&var]: var 변수를 참조로 캡처
    • [=]: 람다 내부에서 사용되는 모든 외부 변수를 값으로 캡처
    • [&]: 람다 내부에서 사용되는 모든 외부 변수를 참조로 캡처
    • [this]: 현재 객체의 this 포인터를 캡처
  • (매개변수): 람다 함수가 받을 매개변수 목록입니다.
  • -> 반환타입: 반환 타입입니다. 생략 가능하며, 컴파일러가 추론합니다.
  • { 함수 본문 }: 람다 함수가 수행할 코드입니다.
람다 표현식 사용 예시
#include <iostream>
#include <vector>
#include <algorithm> // for_each, sort
#include <string>

struct Person {
    std::string name;
    int age;
};

std::ostream& operator<<(std::ostream& out, const Person& p) {
    return out << "[" << p.name << ", " << p.age << "]";
}

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    int valueToAdd = 10; // 외부 변수

    std::cout << "Adding " << valueToAdd << " and printing (lambda): ";
    // 람다를 사용하여 AddAndPrint 함수 객체 역할을 대체
    std::for_each(numbers.begin(), numbers.end(), [valueToAdd](int n){ // valueToAdd를 값으로 캡처
        std::cout << n + valueToAdd << " ";
    });
    std::cout << std::endl; // 출력: 11 12 13 14 15

    std::cout << "-------------------------------------------\n";

    std::vector<std::string> names = {"Alice", "Bob", "Charlie", "David"};

    // 길이가 5보다 긴 이름만 출력 (람다 사용)
    std::cout << "Names longer than 5: ";
    std::for_each(names.begin(), names.end(), [](const std::string& name){
        if (name.length() > 5) {
            std::cout << name << " ";
        }
    });
    std::cout << std::endl; // 출력: Charlie

    // 복잡한 정렬 조건 (람다 사용)
    std::vector<Person> people = {
        {"Alice", 30},
        {"Charlie", 25},
        {"Bob", 35},
        {"Amy", 25}
    };

    std::cout << "Original people:\n";
    for (const auto& p : people) {
        std::cout << p << " ";
    }
    std::cout << std::endl;

    // 나이가 같으면 이름으로, 다르면 나이로 정렬
    std::sort(people.begin(), people.end(), [](const Person& p1, const Person& p2){
        if (p1.age != p2.age) {
            return p1.age < p2.age; // 나이가 다르면 나이 기준
        }
        return p1.name < p2.name; // 나이가 같으면 이름 기준
    });
    std::cout << "Sorted by age then name:\n";
    for (const auto& p : people) {
        std::cout << p << " ";
    }
    std::cout << std::endl; // 출력: [Amy, 25] [Charlie, 25] [Alice, 30] [Bob, 35]

    return 0;
}

람다는 특히 짧은 함수 객체가 필요한 경우에 매우 강력하고 편리합니다. 값 캡처는 람다를 만든 시점의 값을 별도 상태로 갖고, 참조 캡처는 살아 있는 원래 저장소를 사용합니다. 원 예제의 valueToAdd가 호출 전에 바뀌지 않아서 두 방식을 바꿔도 같은 출력처럼 보일 수 있지만, 일반 규칙이 같다는 뜻은 아닙니다.

예시 아래 코드는 두 람다와 offset이 같은 main 안에 살아 있는 동안 호출하므로 참조 대상의 수명도 유지됩니다.

만들 때 복사한 값과 호출 때 읽는 저장소
#include <iostream>

int main() {
    int offset = 10;
    auto byValue = [offset](int n) { return n + offset; };
    auto byReference = [&offset](int n) { return n + offset; };
    offset = 20;
    std::cout << byValue(1) << ' ' << byReference(1) << '\n';
}

출력은 11 21입니다. 첫 람다는 만든 때의 10, 둘째는 현재 offset의 20을 사용합니다. 참조 캡처한 람다를 오래 보관한다고 offset의 수명도 늘어나는 것은 아닙니다. 값·참조 캡처 규칙을 확인하고, 사라진 지역 변수를 참조하는 호출을 실행하지 않습니다.

알고리즘이 받은 객체의 상태 확인하기 호출 객체를 값으로 넘기면 원 객체와 알고리즘이 받은 객체가 다를 수 있습니다. 다음은 실행 정책 인수가 없는 순차 std::for_each입니다. 이 오버로드는 각 원소에 적용한 함수 객체를 반환하므로 반환 객체에서 누적 상태를 받습니다.

원 함수 객체와 순회 후 반환 객체
#include <iostream>
#include <vector>
#include <algorithm>

struct Sum {
    int total = 0;
    void operator()(int n) { total += n; }
};

int main() {
    std::vector<int> values = {1, 2, 3};
    Sum initial;
    Sum result = std::for_each(values.begin(), values.end(), initial);
    std::cout << initial.total << ' ' << result.total << '\n';
}

출력은 0 6입니다. initial을 복사해 받은 객체가 0→1→3→6으로 변하며, 원 initial은 0입니다. 이 설명은 순차 for_each의 반환 계약에 관한 것으로, 모든 알고리즘이나 실행 정책 오버로드가 함수 객체의 누적 상태를 반환한다고 일반화하지 않습니다.

스스로 확인하기 ① 첫 프로그램에서 offset의 초기값을 2, 변경값을 5로 바꾸고 두 람다를 인수 4로 호출하면 출력은 무엇인가요? ② Sum 프로그램의 values를 {4, 1}로 바꾸고 호출 전에 initial.total = 10;을 넣으면 원 객체와 결과 객체는 각각 무엇을 갖나요? 원 values도 누적 합계로 바뀌는지 판단합니다.

캡처와 값 전달의 저장 상태 풀이

① 출력은 6 9입니다. 값 캡처는 2를 보관하고 참조 캡처는 현재 5를 읽습니다. ② 출력은 10 15입니다. 복사 객체는 10→14→15가 되지만 initial은 10을 유지합니다. Sum의 int 인수는 값 복사이므로 values는 4·1 그대로입니다. 함수 객체의 멤버를 변경하는 것과 입력 원소를 변경하는 것은 서로 다른 저장소에 대한 동작입니다.

복잡한 함수 객체는 여전히 클래스로 정의하는 것이 더 명확할 수 있습니다.

함수 객체는 operator()와 람다, 표준 함수 객체가 알고리즘에 전달되는 방식을 보여 줍니다.