C ++에서 콜백 함수를 구현할 때 여전히 C 스타일 함수 포인터를 사용해야합니까?
void (*callbackFunc)(int);
또는 std :: function을 사용해야합니까?
std::function< void(int) > callbackFunc;
C ++에서 콜백 함수를 구현할 때 여전히 C 스타일 함수 포인터를 사용해야합니까?
void (*callbackFunc)(int);
또는 std :: function을 사용해야합니까?
std::function< void(int) > callbackFunc;
답변:
요컨대,std::function
이유가없는 한 사용하십시오.
함수 포인터는 일부 컨텍스트 를 캡처 할 수 없다는 단점이 있습니다. 예를 들어 람다 함수를 일부 컨텍스트 변수를 캡처하는 콜백으로 전달할 수는 없지만 캡처하지 않으면 작동합니다. 따라서 객체 (- this
포인터)를 캡처해야 하므로 객체의 멤버 변수를 호출하는 것도 불가능합니다 . (1)
std::function
(C ++ 11부터)는 주로 함수 를 저장 하는 것입니다 (전달하면 저장하지 않아도 됨). 따라서 콜백을 멤버 변수에 저장하려면 아마도 최선의 선택 일 것입니다. 그러나 저장하지 않으면 호출 할 때 (매우 작은) 오버 헤드가 발생한다는 단점이 있지만 (성능이 중요한 상황에서는 문제가 될 수 있지만 대부분의 경우) 좋은 "첫 번째 선택"입니다. 안됩니다). 매우 "유니버설"입니다. 일관성 있고 읽을 수있는 코드에 대해 많은 관심을 갖고 있고 모든 선택에 대해 생각하고 싶지 않은 경우 (즉, 단순하게 유지하려는 경우) std::function
전달하는 모든 함수에 사용하십시오 .
세 번째 옵션에 대해 생각 : 당신이 다음 제공된 콜백 함수를 통해 뭔가를보고 작은 기능을 구현하는 데 약이 있다면하는 생각 템플릿 매개 변수 다음이 될 수 있는 호출 객체 , 즉, 함수 포인터, 펑터, 람다, a std::function
, ... 여기의 단점은 (외부) 함수가 템플릿이되므로 헤더에 구현해야한다는 것입니다. 반면에 (외부) 함수의 클라이언트 코드가 콜백에 대한 호출을 "인식"하면 사용 가능한 정확한 유형 정보가 표시되므로 콜백에 대한 호출이 인라인 될 수 있다는 이점이 있습니다.
템플리트 매개 변수가있는 버전의 예 ( C ++ 11 이전 &
대신 쓰기 &&
) :
template <typename CallbackFunction>
void myFunction(..., CallbackFunction && callback) {
...
callback(...);
...
}
다음 표에서 볼 수 있듯이 모든 장단점이 있습니다.
+-------------------+--------------+---------------+----------------+
| | function ptr | std::function | template param |
+===================+==============+===============+================+
| can capture | no(1) | yes | yes |
| context variables | | | |
+-------------------+--------------+---------------+----------------+
| no call overhead | yes | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be inlined | no | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be stored | yes | yes | no(2) |
| in class member | | | |
+-------------------+--------------+---------------+----------------+
| can be implemented| yes | yes | no |
| outside of header | | | |
+-------------------+--------------+---------------+----------------+
| supported without | yes | no(3) | yes |
| C++11 standard | | | |
+-------------------+--------------+---------------+----------------+
| nicely readable | no | yes | (yes) |
| (my opinion) | (ugly type) | | |
+-------------------+--------------+---------------+----------------+
(1)이 한계를 극복하기위한 대안이 있습니다 (예 : 추가 데이터를 추가 매개 변수로 (외부) 함수에 전달 : myFunction(..., callback, data)
will call) callback(data)
. 이것이 C 스타일의 "인수를 이용한 콜백"입니다. C ++에서 사용할 수 있지만 (WIN32 API에서 많이 사용되는 방식으로) C ++에서 더 나은 옵션이 있으므로 피해야합니다.
(2) 클래스 템플릿에 대해 이야기하지 않는 한, 즉 함수를 저장하는 클래스는 템플릿입니다. 그러나 이것은 클라이언트 측에서 함수의 유형이 콜백을 저장하는 객체의 유형을 결정한다는 것을 의미합니다. 실제 사용 사례에는 거의 옵션이 아닙니다.
(3) C ++ 11 이전 버전의 경우 boost::function
std::function
를 사용하면 즉시 외부에 저장해야하는 경우 지워진 템플릿 함수로 변환 할 수있는 효율적인 템플릿 함수를 생성 할 수 있습니다) 컨텍스트라고 함).
void (*callbackFunc)(int);
C 스타일 콜백 함수 일 수 있지만 좋지 않은 디자인 중 끔찍하게 사용할 수없는 함수입니다.
잘 설계된 C 스타일 콜백 모습처럼 void (*callbackFunc)(void*, int);
- 그것은이이 void*
기능을 넘어 상태를 유지하기 위해 콜백을 수행하는 코드를 허용 할 수 있습니다. 이렇게하지 않으면 호출자가 전 세계적으로 상태를 저장하게됩니다. 이는 무례한 것입니다.
std::function< int(int) >
int(*)(void*, int)
대부분의 구현에서 호출 보다 약간 더 비쌉니다 . 그러나 일부 컴파일러는 인라인하기가 더 어렵습니다. std::function
라이브러리로 들어갈 수있는 함수 포인터 호출 오버 헤드 ( '가장 빠른 대리자'등)에 필적 하는 복제 구현이 있습니다.
이제 콜백 시스템의 클라이언트는 콜백을 생성 및 제거 할 때 리소스를 설정하고 처리해야하며 콜백의 수명을 인식해야합니다. void(*callback)(void*, int)
이것을 제공하지 않습니다.
때로는 코드 구조 (콜백의 수명이 제한되어 있음) 또는 다른 메커니즘 (등록 취소 콜백 등)을 통해 사용할 수 있습니다.
std::function
제한된 수명 관리 수단을 제공합니다 (물론 잊어 버렸을 때 객체의 마지막 사본이 사라짐).
일반적으로 std::function
성능 관련 문제 없는 한 하지 않을 입니다. 그렇다면 픽셀 당 콜백 대신 구조적 변경 사항을 살펴보고, 전달하는 람다를 기반으로 스캔 라인 프로세서를 생성하는 방법은 무엇입니까? 이는 함수 호출 오버 헤드를 사소한 수준으로 줄이는 데 충분합니다. ). 그런 다음 지속되면 delegate
가능한 가장 빠른 대리인을 기반으로 작성 하고 성능 문제가 사라지는 지 확인합니다.
나는 대부분 레거시 API 또는 다른 컴파일러 생성 코드 간 통신을 위해 C 인터페이스를 만들기 위해 함수 포인터 만 사용합니다. 또한 점프 테이블, 유형 삭제 등을 구현할 때 내부 구현 세부 정보로 사용했습니다. 생성 및 소비하고 클라이언트 코드에서 사용할 수 있도록 외부에서 노출하지 않고 함수 포인터가 필요한 모든 것을 수행합니다. .
적절한 콜백 수명 관리 인프라가 있다고 가정하면 스타일 콜백 std::function<int(int)>
으로 바뀌는 래퍼를 작성할 수 있습니다 int(void*,int)
. 따라서 모든 C 스타일 콜백 수명 관리 시스템에 대한 연기 테스트로서, 나는 포장이 std::function
합리적으로 잘 작동 하는지 확인합니다 .
void*
에서 왔습니까? 왜 함수 이상의 상태를 유지하고 싶습니까? 함수는 필요한 모든 코드, 모든 기능을 포함해야하며 원하는 인수를 전달하고 무언가를 수정하고 반환하면됩니다. 외부 상태가 필요한 경우 functionPtr 또는 콜백이 왜 해당 수하물을 운반합니까? 콜백은 불필요하게 복잡하다고 생각합니다.
this
. 멤버 함수가 호출되는 경우를 고려해야하기 때문에 this
객체의 주소를 가리키는 포인터 가 필요 합니까? 내가 틀렸다면 그것에 대해 더 많이 찾을 수 없기 때문에 더 많은 정보를 찾을 수있는 링크를 줄 수 있습니까? 미리 감사드립니다.
void*
은 런타임 상태의 전송을 허용 하기 위해를 사용합니다. a void*
및 void*
인수가 있는 함수 포인터 는 객체에 대한 멤버 함수 호출을 에뮬레이션 할 수 있습니다. 죄송합니다. "C 콜백 메커니즘 설계 101"을 안내하는 리소스를 모릅니다.
this
입니다. 그게 내 뜻이야 그래, 고마워
피해야 할 유일한 이유 std::function
는 C ++ 11에 도입 된이 템플릿에 대한 지원이없는 레거시 컴파일러의 지원입니다.
C ++ 11 이전 언어를 지원할 필요가없는 경우를 사용 std::function
하면 호출자가 콜백을 구현할 때 더 많은 선택권 을 부여하므로 "일반"함수 포인터에 비해 더 나은 옵션이됩니다. 콜백을 수행하는 코드에 대한 구현의 세부 사항을 추상화하면서 API 사용자에게 더 많은 선택권을 제공합니다.