목차
- [[#고차 함수(Higher-order function)|고차 함수(Higher-order function)]]
- [[#함수 포인터|함수 포인터]]
- [[#라이브러리 핸들(dll 함수 호출)|라이브러리 핸들(dll 함수 호출)]]
- [[#투명 연산자 펑터|투명 연산자 펑터]]
- [[#::bind()|::bind()]]
- [[#람다|람다]]
- [[#std::Invoke|std::Invoke]]
- [[#트레일링 리턴타입을 쓰는 이유|트레일링 리턴타입을 쓰는 이유]]
- [[#트레일링 리턴타입을 쓰는 이유#✅ 1. 리턴 타입을 명확히 하고 싶을 때|✅ 1. 리턴 타입을 명확히 하고 싶을 때]]
- [[#트레일링 리턴타입을 쓰는 이유#✅ 2. 리턴 타입이 **복잡하거나 추론 불가능할 때|✅ 2. 리턴 타입이 **복잡하거나 추론 불가능할 때]]
- [[#트레일링 리턴타입을 쓰는 이유#✅ 3. 리턴 타입이
auto
인 경우라도 const ref 등 복잡한 타입이면|✅ 3. 리턴 타입이auto
인 경우라도 const ref 등 복잡한 타입이면]] - [[#트레일링 리턴타입을 쓰는 이유#✅ 정리|✅ 정리]]
고차 함수(Higher-order function)
다른 함수들을 인자로 받거나, 결과값으로 반환하는 함수
- 함수 포인터,
function
- 함수 포인터,
현대에서 함수 포인터보다
function
이 많이 사용됨 (편리성, 가독성, 타입 안정성 등)특징
함수를 인자로 받을 수 있음
#include <iostream> #include <functional> void applyFunction(std::function<int(int)> func, int value) { std::cout << "Result: " << func(value) << std::endl; } int square(int x) { return x * x; } int main() { applyFunction(square, 5); // square 함수를 전달 return 0; }
함수를 반환할 수 있음
#include <iostream> #include <functional> std::function<int(int)> multiplier(int factor) { return [factor](int x) { return x * factor; }; } int main() { auto timesTwo = multiplier(2); std::cout << "2 x 5 = " << timesTwo(5) << std::endl; // 출력: 2 x 5 = 10 return 0; }
함수 포인터
함수의 주소를 저장하고 참조하는 데 사용되는 포인터
멤버 함수를 전달하기 위해서는
std::bind
를 통해서 객체와 결합된 Wrapper 필요사용 예시
#include <iostream> using namespace std; // 함수 정의 int add(int a, int b) { return a + b; } int main() { // 함수 포인터 선언 int (*funcPtr)(int, int) = add; // 함수 포인터 호출 cout << "Result: " << funcPtr(5, 3) << endl; // 출력: Result: 8 return 0; }
함수 테이블로 사용 예시
int add(int a, int b) { return a + b; } int subtract(int a, int b) { return a - b; } int main() { // 함수 포인터 배열 int (*operations[2])(int, int) = {add, subtract}; cout << "Add: " << operations[0](5, 3) << endl; // 출력: Add: 8 cout << "Subtract: " << operations[1](5, 3) << endl; // 출력: Subtract: 2 return 0; }
Wrapper 이용 멤버 함수 전달
#include <iostream> #include <functional> class MyClass { public: void memberCallback(int value) { std::cout << "Member function called with value: " << value << std::endl; } }; void executeCallback(const std::function<void(int)>& callback, int arg) { callback(arg); } int main() { MyClass obj; // std::function을 사용하여 멤버 함수 래핑 std::function<void(int)> callback = std::bind(&MyClass::memberCallback, &obj); // 콜백 함수 실행 executeCallback(callback, 42); return 0; }
라이브러리 핸들(dll 함수 호출)
HMODULE lib { ::LoadLibrary("hardware.dll") };
- 이렇게 호출해서 리턴된 결과를 라이브러리 핸들이라 함
- 호출 과정에서 error발생 시, NULL 리턴
- 타입 앨리어스를 이용해서 프로토타입을 가진 함수에 대한 포인터 이름 정의 가능
using ConnectFunction = int(__stdcall*)(bool, int, const char*);
- DLL에 있는 함수 포인터 구하기
ConnectFunction connect { (ConnectFunction)::GetProcAddress(lib, "Connect");}
투명 연산자 펑터
- 비투명 연산자 펑터보다 성능이 우수 (가능하면 투명 연산자 펑터 사용)
- 타입을 추론하여 읽기 떄문에, 형변환이 발생x
multiplies<int> -> multiplies<>
로 적어도 됨- 이종 룩업 : C14에 도입;
- 연관 컨테이너에서 키 타입과 다른 타입을 사용하여 검색 수행할 수 있도록 지원
set<string, less<>> mySet;
auto i1 { mySet.find("Key") }; // 스트링 생기지도 않고 메모리 할당x
auto i2 { mySet.find("Key"sv) }; // 스트링 생기지도 않고 메모리 할당x
::bind()
<functional>
의 함수- 함수 포인터가 멤버 변수를 전달할 떄, 객체와 wrapping 하기위해 많이 사용
bind()
의 매개변수는 기본적으로 복제본을 생성bind(func, ref(index))
를 하면 참조로 넘김
람다
람다 선언자(lambda introducer), 캡처 블록 :
[]
- 변수를 캡처해서 람다 표현식 본문에 쓸 떄는, 해당 대괄호 안에 넣기
기본 사용 예제
auto basicLambda { []{ cout << "Hello from Lambda" << endl; } }; basicLambda();
람다 클로저 : 컴파일러는 모든 람다 표현식을 자동으로 함수 객체로 변환
트레일링 리턴타입 : ->
람다의
()
오퍼레이터 호출은 기본적으로const
, 따라서 멤버변수(캡쳐변수)의 값 변경이 불가mutable
변수를 캡처변수로 사용 시에는 변경 가능- 캡처변수에
&
를 붙이면 레퍼런스로 캡처 시, 변경 가능
캡처 디폴트 : 람다 표현식이 속한 상위 스코프의 변수를 모두 캡처하는 방법
[=]
: 스코프에 있는 변수를 모두 값으로 캡처[&]
: 스코프에 있는 변수를 모두 레퍼런스로 캡처[=, &x, &y]
,[&, x]
등 여러 조합으로 가능[*this]
: 현재 객체의 복제본을 캡처 (실행 시점에 객체가 살아 있지 않을 떄, 유용)
[p = move(myPtr)]{}
와 같이std::move
를 비롯한 모든 종류의 표현식으로 초기화 가능
람다 표현식을 함수의 return 타입으로 반환 가능
C14 에서는 auto 함수 리턴 추론을 통해 쉽게 사용가능
std::Invoke
- 모든 콜러블 객체에 대해 일련의 매개변수를 지정해서 호출 가능
invoke({콜러블객체}, {매개변수})
트레일링 리턴타입을 쓰는 이유
✅ 1. 리턴 타입을 명확히 하고 싶을 때
컴파일러가 리턴 타입을 제대로 추론하지 못하거나, 오버로드, 템플릿 등의 상황에서 헷갈릴 수 있을 때 명시적으로 써주는 게 좋아요.
auto func = [](bool b) -> int {
if (b) return 1;
else return 2;
};
이 경우는 사실 없어도 되지만, 복잡한 상황에선 명시가 도움이 됩니다.
✅ 2. 리턴 타입이 **복잡하거나 추론 불가능할 때
특히 auto
키워드가 리턴 타입에 들어가는 경우나, 템플릿 코드에서는 반드시 써야 할 때도 있어요.
예:
template <typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
여기선 a + b
의 타입을 컴파일러가 추론하도록 하면서도, 템플릿 밖에서 리턴 타입을 지정해야 하기 때문에 -> decltype(...)
이 필요합니다.
람다에서도 똑같아요:
auto l = [](auto a, auto b) -> decltype(a + b) {
return a + b;
};
✅ 3. 리턴 타입이 auto
인 경우라도 const ref 등 복잡한 타입이면
예:
auto l = [](const std::string& s) -> const std::string& {
return s;
};
이런 식으로 참조 타입, const, 포인터 등을 정확히 명시할 때 ->
는 필요합니다.
✅ 정리
상황 | -> 필요 여부 |
---|---|
간단한 리턴 (int, double 등) | ❌ 없어도 됨 |
리턴 타입 추론 어려움 | ✅ 필요함 |
템플릿, auto, decltype 등 복잡한 타입 | ✅ 필요함 |
const, 참조, 포인터 리턴 등 | ✅ 필요함 |
'공부 > C++' 카테고리의 다른 글
STL활용_vector로 set 대체 (0) | 2025.04.29 |
---|---|
union (0) | 2025.04.10 |
(cpp17)string_view (2) | 2025.04.09 |
예외처리(simple) (0) | 2025.04.09 |
Attribute (0) | 2025.03.27 |