목차
- [[#고차 함수(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
이 많이 사용됨 (편리성, 가독성, 타입 안정성 등)
특징
함수를 인자로 받을 수 있음
#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, 참조, 포인터 리턴 등 |
✅ 필요함 |