死灵术。
我认为迄今为止的答案有些不太清晰。
让我们举个例子:
假设您有一个像素数组(ARGB int8_t值的数组)
// A RGB image
int8_t* pixels = new int8_t[1024*768*4];
现在您想生成一个PNG格式的图片。为此,您需要调用toJpeg函数。
bool ok = toJpeg(writeByte, pixels, width, height);
其中writeByte是一个回调函数
void writeByte(unsigned char oneByte)
{
fputc(oneByte, output);
}
这里的问题是:FILE*输出必须是全局变量。
如果您处于多线程环境中(例如http服务器),那么情况会非常糟糕。
因此,您需要一些方法使输出成为非全局变量,同时保留回调签名。
最直接的解决方案是使用闭包,我们可以使用具有成员函数的类来模拟它。
class BadIdea {
private:
FILE* m_stream;
public:
BadIdea(FILE* stream) {
this->m_stream = stream;
}
void writeByte(unsigned char oneByte){
fputc(oneByte, this->m_stream);
}
};
然后执行
FILE *fp = fopen(filename, "wb");
BadIdea* foobar = new BadIdea(fp);
bool ok = TooJpeg::writeJpeg(foobar->writeByte, image, width, height);
delete foobar;
fflush(fp);
fclose(fp);
然而,与预期相反,这并不起作用。
原因是,C++成员函数有点像C#的扩展函数。
所以你有
class/struct BadIdea
{
FILE* m_stream;
}
和
static class BadIdeaExtensions
{
public static writeByte(this BadIdea instance, unsigned char oneByte)
{
fputc(oneByte, instance->m_stream);
}
}
当您想调用writeByte时,需要传递的不仅是writeByte的地址,还需要传递BadIdea实例的地址。
因此,当您有一个writeByte过程的typedef时,它看起来像这样:
typedef void (*WRITE_ONE_BYTE)(unsigned char);
你有一个名为 writeJpeg 的签名,看起来像这样
bool writeJpeg(WRITE_ONE_BYTE output, uint8_t* pixels, uint32_t
width, uint32_t height))
{ ... }
基本上,如果不修改writeJpeg函数,将两个地址成员函数传递给一个地址函数指针是不可能的,这是无法避免的。
在C++中,下一步最好的做法是使用lambda函数:
FILE *fp = fopen(filename, "wb");
auto lambda = [fp](unsigned char oneByte) { fputc(oneByte, fp); };
bool ok = TooJpeg::writeJpeg(lambda, image, width, height);
然而,由于 lambda 并没有做任何不同的事情,只是将实例传递给一个隐藏类(例如“BadIdea”类),因此您需要修改 writeJpeg 的签名。
使用 lambda 的优点是您只需要更改一个 typedef 而不是手动创建一个类。
typedef void (*WRITE_ONE_BYTE)(unsigned char);
到
using WRITE_ONE_BYTE = std::function<void(unsigned char)>
然后您可以将其余内容保持不变。
您还可以使用 std::bind。
auto f = std::bind(&BadIdea::writeByte, &foobar);
但在幕后,这只是创建了一个lambda函数,然后还需要更改typedef才能完成。因此,没有办法将成员函数传递给需要静态函数指针的方法。但是,如果您对源代码有控制权,则lambda是简单易行的解决方法。否则,您就没那么幸运了。在C++中你什么也做不了。
注意:std::function需要#include
但是,由于C++允许您使用C,因此,如果您不介意链接依赖项,您可以在纯C中使用libffcall(
libffcall)。从GNU下载libffcall(至少在ubuntu上,请勿使用发行版提供的软件包-它已损坏),然后解压缩。
./configure
make
make install
gcc main.c -l:libffcall.a -o ma
main.c:
#include <callback.h>
void function (void* data, va_alist alist)
{
int abc = va_arg_int(alist);
printf("data: %08p\n", data);
printf("abc: %d\n", abc);
}
int main(int argc, char* argv[])
{
int in1 = 10;
void * data = (void*) 20;
void(*incrementer1)(int abc) = (void(*)()) alloc_callback(&function, data);
incrementer1(123);
return EXIT_SUCCESS;
}
如果您使用CMake,请在add_executable之后添加链接库。
add_library(libffcall STATIC IMPORTED)
set_target_properties(libffcall PROPERTIES
IMPORTED_LOCATION /usr/local/lib/libffcall.a)
target_link_libraries(BitmapLion libffcall)
或者您可以动态链接libffcall。
target_link_libraries(BitmapLion ffcall)
注意:
您可能需要包含libffcall头文件和库,或使用libffcall的内容创建一个cmake项目。
#include
int add(int a, int b) {
return a + b;
}
int main() {
auto add_2 = std::bind1st(std::ptr_fun(add), 2); // 绑定add函数的第一个参数为2
int r = add_2(3); // 调用add_2相当于调用add(2, 3)
return 0;
}
这里使用了std::ptr_fun
来将函数指针转换为函数对象,从而符合std::bind1st
的参数要求。因此,在本题中,如果想使用std::bind1st
来实现绑定第一个参数为m
的std::less<int>
比较器,可以这样写:#include
int main() {
int m = 10;
auto less_m = std::bind1st(std::less(), m);
bool r = less_m(5); // 相当于调用 std::less()(m, 5)
return 0;
}
- mabrahamerror: call to non-static member function without an object argument
。 - Apollys supports Monica