[C++23 새기능 소개] std::move_only_function

C++23에서는 함수 객체와 콜백을 더욱 안전하고 유연하게 관리하기 위한 새로운 도구인 std::move_only_function이 도입되었습니다. 이번 글에서는 std::move_only_function의 개념과 사용법, 그리고 이전 버전과 비교하여 어떻게 개선되었는지 알아보겠습니다.

std::move_only_function이란 무엇인가요?

std::move_only_function이동 전용(move-only) 함수 래퍼로, 복사가 불가능한 함수 객체를 안전하게 다룰 수 있게 해줍니다. 이는 기존의 std::function과 유사하지만, 복사 가능성 요구 사항을 제거하여 이동만 가능한 함수 객체를 저장하고 호출할 수 있습니다. 이를 통해 캡처된 상태를 안전하게 이동하고, 성능 향상을 기대할 수 있습니다.

이전 버전에서는 어떻게 했나요?

C++23 이전에는 콜백이나 함수 객체를 저장하고 호출하기 위해 주로 std::function을 사용했습니다.

예제: std::function 사용

#include <functional>
#include <iostream>

void execute(std::function<void()> func) {
    func();
}

int main() {
    int value = 42;
    auto lambda = [value]() { std::cout << "값: " << value << '\n'; };

    execute(lambda);

    return 0;
}
  • 문제점:
    • std::function은 내부적으로 함수 객체를 복사하여 저장합니다.
    • 복사가 불가능한 함수 객체(예: 이동만 가능한 캡처를 가진 람다)는 std::function에 저장할 수 없습니다.
    • 불필요한 복사로 인해 성능 저하가 발생할 수 있습니다.

복사가 불가능한 함수 객체의 처리 어려움

#include <functional>
#include <iostream>
#include <memory>

void execute(std::function<void()> func) {
    func();
}

int main() {
    auto unique_ptr = std::make_unique<int>(42);
    auto lambda = [ptr = std::move(unique_ptr)]() {
        std::cout << "값: " << *ptr << '\n';
    };

    // 컴파일 오류: 복사 불가능한 람다를 std::function에 저장할 수 없음
    // execute(lambda);

    return 0;
}
  • 문제점:
    • 이동 캡처된 unique_ptr은 복사할 수 없는 람다를 생성합니다.
    • std::function은 복사 가능해야 하므로, 이러한 람다를 저장할 수 없습니다.

C++23의 std::move_only_function을 사용한 개선

std::move_only_function은 이동 전용 함수 래퍼로, 복사가 불가능한 함수 객체를 이동하여 저장할 수 있습니다.

예제: std::move_only_function 사용

#include <functional>
#include <iostream>
#include <memory>

void execute(std::move_only_function<void()> func) {
    func();
}

int main() {
    auto unique_ptr = std::make_unique<int>(42);
    auto lambda = [ptr = std::move(unique_ptr)]() {
        std::cout << "값: " << *ptr << '\n';
    };

    execute(std::move(lambda));

    return 0;
}
  • std::move_only_function은 복사 가능성을 요구하지 않으므로, 이동만 가능한 함수 객체를 저장할 수 있습니다.
  • 함수 객체를 이동하여 전달하므로, 불필요한 복사가 발생하지 않습니다.
  • 성능 향상자원 관리의 안전성을 확보할 수 있습니다.

어떻게 좋아졌나요?

  • 복사 불가능한 함수 객체 지원: 이동만 가능한 람다나 함수 객체를 안전하게 저장하고 호출할 수 있습니다.
  • 성능 향상: 불필요한 복사를 제거하여 성능 오버헤드를 줄일 수 있습니다.
  • 자원 관리 개선: 이동 캡처를 통한 자원 소유권 이전이 원활해집니다.
  • 유연성 증가: 함수 객체의 복사 가능성 여부와 상관없이 일관된 인터페이스를 제공할 수 있습니다.

상세한 예제와 비교

1. 이동 캡처된 람다의 처리

기존 방식

#include <functional>
#include <iostream>
#include <memory>

void execute(auto func) {
    func();
}

int main() {
    auto unique_ptr = std::make_unique<int>(42);
    auto lambda = [ptr = std::move(unique_ptr)]() {
        std::cout << "값: " << *ptr << '\n';
    };

    execute(std::move(lambda));

    // 그러나, 함수 포인터나 std::function을 요구하는 API에는 전달할 수 없음

    return 0;
}
  • 제한점:
    • auto를 사용하여 함수를 직접 전달할 수 있지만, 일반화된 함수 포인터나 콜백 인터페이스에는 전달할 수 없습니다.

C++23 방식

#include <functional>
#include <iostream>
#include <memory>

void registerCallback(std::move_only_function<void()> func) {
    // 콜백을 저장하거나 즉시 실행
    func();
}

int main() {
    auto unique_ptr = std::make_unique<int>(42);
    auto lambda = [ptr = std::move(unique_ptr)]() {
        std::cout << "값: " << *ptr << '\n';
    };

    registerCallback(std::move(lambda));

    return 0;
}
  • 이동만 가능한 함수 객체를 일관된 인터페이스로 처리할 수 있습니다.

2. 콜백 저장소에서의 활용

#include <functional>
#include <vector>

class EventDispatcher {
public:
    void addListener(std::move_only_function<void(int)> listener) {
        listeners_.emplace_back(std::move(listener));
    }

    void dispatch(int event) {
        for (auto& listener : listeners_) {
            listener(event);
        }
    }

private:
    std::vector<std::move_only_function<void(int)>> listeners_;
};

int main() {
    EventDispatcher dispatcher;

    auto unique_ptr = std::make_unique<int>(100);
    dispatcher.addListener([ptr = std::move(unique_ptr)](int event) {
        std::cout << "이벤트: " << event << ", 값: " << *ptr << '\n';
    });

    dispatcher.dispatch(1);

    return 0;
}
  • 복사 불가능한 함수 객체를 콜백 리스트에 저장하여 이벤트 처리에 활용할 수 있습니다.

주의 사항

  • 이동 후 사용 불가: std::move_only_function은 이동 후 비어있는 상태가 되므로, 이동 후에는 사용할 수 없습니다.
  • 복사 불가능: 이름에서 알 수 있듯이, std::move_only_function은 복사할 수 없습니다. 복사가 필요한 경우 std::function을 사용해야 합니다.
  • 호환성 고려: 기존 코드에서 std::function을 std::move_only_function으로 대체할 때는 복사 여부를 고려해야 합니다.
  • 컴파일러 지원: std::move_only_function은 C++23 기능이므로, 이를 지원하는 컴파일러와 표준 라이브러리가 필요합니다.

std::function과 std::move_only_function의 비교

특징 std::function std::move_only_function

복사 가능성 가능 불가능 (이동만 가능)
이동 가능성 가능 가능
복사 불가능한 객체 지원 불가능 가능
사용 사례 일반적인 함수 객체 저장 이동만 가능한 함수 객체 저장

요약

C++23의 std::move_only_function은 복사 불가능한 함수 객체를 안전하고 효율적으로 다룰 수 있는 강력한 도구입니다. 이전에는 std::function을 사용하여 함수 객체를 저장했지만, 복사 가능성 요구 사항으로 인해 이동만 가능한 함수 객체를 처리할 수 없었습니다. 이제 std::move_only_function을 통해 이동 캡처된 람다 등을 자유롭게 활용하여 코드의 유연성과 성능을 향상시킬 수 있습니다.

 

참고 자료:

 

반응형