Thăng bằng.
Tôi nghĩ rằng câu trả lời cho đến nay là một chút không rõ ràng.
Hãy làm một ví dụ:
Giả sử bạn có một mảng pixel (mảng giá trị ARGB int8_t)
int8_t* pixels = new int8_t[1024*768*4];
Bây giờ bạn muốn tạo một PNG. Để làm như vậy, bạn gọi hàm toJpeg
bool ok = toJpeg(writeByte, pixels, width, height);
trong đó writeByte là một hàm gọi lại
void writeByte(unsigned char oneByte)
{
fputc(oneByte, output);
}
Vấn đề ở đây: đầu ra FILE * phải là một biến toàn cục.
Rất tệ nếu bạn đang ở trong môi trường đa luồng (ví dụ: máy chủ http).
Vì vậy, bạn cần một số cách để làm cho đầu ra trở thành một biến không phải toàn cục, trong khi vẫn giữ lại chữ ký gọi lại.
Giải pháp ngay lập tức nảy sinh trong tâm trí là một đóng, chúng ta có thể mô phỏng bằng cách sử dụng một lớp có hàm thành viên.
class BadIdea {
private:
FILE* m_stream;
public:
BadIdea(FILE* stream) {
this->m_stream = stream;
}
void writeByte(unsigned char oneByte){
fputc(oneByte, this->m_stream);
}
};
Và sau đó làm
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);
Tuy nhiên, trái với mong đợi, điều này không hiệu quả.
Lý do là, các hàm thành viên C ++ được thực hiện giống như các hàm mở rộng C #.
Vì vậy, bạn có
class/struct BadIdea
{
FILE* m_stream;
}
và
static class BadIdeaExtensions
{
public static writeByte(this BadIdea instance, unsigned char oneByte)
{
fputc(oneByte, instance->m_stream);
}
}
Vì vậy, khi bạn muốn gọi writeByte, bạn cần chuyển không chỉ địa chỉ của writeByte, mà còn cả địa chỉ của BadIdea-instance.
Vì vậy, khi bạn có một typedef cho thủ tục writeByte và nó trông như thế này
typedef void (*WRITE_ONE_BYTE)(unsigned char);
Và bạn có một chữ ký writeJpeg trông như thế này
bool writeJpeg(WRITE_ONE_BYTE output, uint8_t* pixels, uint32_t
width, uint32_t height))
{ ... }
Về cơ bản là không thể chuyển một hàm thành viên hai địa chỉ sang con trỏ hàm một địa chỉ (mà không sửa đổi writeJpeg) và không có cách nào để giải quyết.
Điều tốt nhất tiếp theo mà bạn có thể làm trong C ++, là sử dụng hàm lambda:
FILE *fp = fopen(filename, "wb");
auto lambda = [fp](unsigned char oneByte) { fputc(oneByte, fp); };
bool ok = TooJpeg::writeJpeg(lambda, image, width, height);
Tuy nhiên, vì lambda không làm gì khác ngoài việc chuyển một thể hiện cho một lớp ẩn (chẳng hạn như -class "BadIdea"), bạn cần phải sửa đổi chữ ký của writeJpeg.
Ưu điểm của lambda so với lớp thủ công là bạn chỉ cần thay đổi một typedef
typedef void (*WRITE_ONE_BYTE)(unsigned char);
đến
using WRITE_ONE_BYTE = std::function<void(unsigned char)>;
Và sau đó bạn có thể để nguyên mọi thứ khác.
Bạn cũng có thể sử dụng std :: bind
auto f = std::bind(&BadIdea::writeByte, &foobar);
Nhưng điều này, đằng sau cảnh, chỉ tạo một hàm lambda, sau đó cũng cần sự thay đổi trong typedef.
Vì vậy, không, không có cách nào để truyền một hàm thành viên đến một phương thức yêu cầu con trỏ hàm tĩnh.
Nhưng lambdas là cách dễ dàng xung quanh, miễn là bạn có quyền kiểm soát nguồn.
Nếu không, bạn không gặp may.
Bạn không thể làm gì với C ++.
Lưu ý:
hàm std :: yêu cầu#include <functional>
Tuy nhiên, vì C ++ cho phép bạn sử dụng cả C, bạn có thể làm điều này với libffcall bằng C đơn giản, nếu bạn không ngại liên kết một phụ thuộc.
Tải xuống libffcall từ GNU (ít nhất là trên ubuntu, không sử dụng gói do distro cung cấp - nó bị hỏng), giải nén.
./configure
make
make install
gcc main.c -l:libffcall.a -o ma
C chính:
#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;
}
Và nếu bạn sử dụng CMake, hãy thêm thư viện trình liên kết sau add_executable
add_library(libffcall STATIC IMPORTED)
set_target_properties(libffcall PROPERTIES
IMPORTED_LOCATION /usr/local/lib/libffcall.a)
target_link_libraries(BitmapLion libffcall)
hoặc bạn có thể liên kết động libffcall
target_link_libraries(BitmapLion ffcall)
Lưu ý:
Bạn có thể muốn bao gồm các tiêu đề và thư viện libffcall hoặc tạo một dự án cmake với nội dung của libffcall.