Làm cách nào để chuyển một hàm thành viên của lớp dưới dạng gọi lại?


76

Tôi đang sử dụng một API yêu cầu tôi chuyển một con trỏ hàm dưới dạng một lệnh gọi lại. Tôi đang cố gắng sử dụng API này từ lớp học của mình nhưng gặp lỗi biên dịch.

Đây là những gì tôi đã làm từ hàm tạo của mình:

m_cRedundencyManager->Init(this->RedundencyManagerCallBack);

Điều này không biên dịch - tôi gặp lỗi sau:

Lỗi 8 lỗi C3867: 'CLoggersInfra :: RedundencyManagerCallBack': cuộc gọi hàm thiếu danh sách đối số; sử dụng '& CLoggersInfra :: RedundencyManagerCallBack' để tạo một con trỏ đến thành viên

Tôi đã thử đề xuất sử dụng &CLoggersInfra::RedundencyManagerCallBack- không phù hợp với tôi.

Bất kỳ đề xuất / giải thích cho điều này?

Tôi đang sử dụng VS2008.

Cảm ơn!!

Câu trả lời:


55

Điều đó không hoạt động vì một con trỏ hàm thành viên không thể được xử lý giống như một con trỏ hàm thông thường, vì nó mong đợi một đối số đối tượng "this".

Thay vào đó, bạn có thể chuyển một hàm thành viên tĩnh như sau, giống như các hàm không phải thành viên bình thường về mặt này:

m_cRedundencyManager->Init(&CLoggersInfra::Callback, this);

Hàm có thể được định nghĩa như sau

static void Callback(int other_arg, void * this_pointer) {
    CLoggersInfra * self = static_cast<CLoggersInfra*>(this_pointer);
    self->RedundencyManagerCallBack(other_arg);
}

4
Wile đây có thể là một giải pháp / cách giải quyết cho OP, tôi không hiểu đây là câu trả lời cho câu hỏi thực tế như thế nào.
Stefan Steiger

1
@StefanSteiger câu trả lời (giải thích) nằm trong đoạn cuối cùng (về bản chất: "con trỏ hàm thành viên không thể được xử lý giống như một con trỏ tới một hàm miễn phí") và gợi ý nên làm gì khác nằm trong các phần khác của câu trả lời của tôi. Đúng là nó có thể phức tạp hơn. Nhưng điều đó không sao và đó là lý do tại sao câu trả lời của tôi không nhận được nhiều sự ủng hộ như những câu khác. Đôi khi nhiều câu trả lời ngắn gọn hơn mà về bản chất chỉ chứa mã cần trợ giúp tốt hơn những câu dài hơn, và đó là lý do tại sao câu trả lời của tôi được chấp nhận.
Johannes Schaub - litb

Schaub: Đúng vậy, chính xác là quan điểm của tôi. Nhưng tôi thấy - bạn nên đã viết phần cuối cùng đầu tiên, và sau đó nói: thay vào đó, bạn có thể làm + này (phần đầu)
Stefan Steiger

125

Đây là một câu hỏi đơn giản nhưng câu trả lời lại phức tạp đến bất ngờ. Câu trả lời ngắn gọn là bạn có thể làm những gì bạn đang cố gắng làm với std::bind1sthoặc boost::bind. Câu trả lời dài hơn ở bên dưới.

Trình biên dịch là chính xác để đề xuất bạn sử dụng &CLoggersInfra::RedundencyManagerCallBack. Đầu tiên, nếu RedundencyManagerCallBacklà một hàm thành viên, thì bản thân hàm không thuộc về bất kỳ trường hợp cụ thể nào của lớp CLoggersInfra. Nó thuộc về chính lớp. Nếu bạn đã từng gọi một hàm lớp tĩnh trước đây, bạn có thể nhận thấy rằng bạn sử dụng cùng một SomeClass::SomeMemberFunctioncú pháp. Vì bản thân hàm là 'tĩnh' theo nghĩa là nó thuộc về lớp chứ không phải là một cá thể cụ thể, bạn sử dụng cùng một cú pháp. Dấu '&' là cần thiết vì về mặt kỹ thuật, bạn không truyền trực tiếp các hàm - các hàm không phải là đối tượng thực trong C ++. Thay vào đó, về mặt kỹ thuật, bạn đang chuyển địa chỉ bộ nhớ cho hàm, tức là một con trỏ đến nơi bắt đầu các lệnh của hàm trong bộ nhớ. Tuy nhiên, hậu quả là giống nhau, bạn có hiệu quả '

Nhưng đó chỉ là một nửa vấn đề trong trường hợp này. Như tôi đã nói, RedundencyManagerCallBackhàm không 'thuộc về' bất kỳ trường hợp cụ thể nào. Nhưng có vẻ như bạn muốn chuyển nó như một cuộc gọi lại với một trường hợp cụ thể trong tâm trí. Để hiểu cách thực hiện điều này, bạn cần hiểu các hàm thành viên thực sự là gì: các hàm thông thường không được định nghĩa-trong-bất kỳ-lớp nào có thêm một tham số ẩn.

Ví dụ:

class A {
public:
    A() : data(0) {}
    void foo(int addToData) { this->data += addToData; }

    int data;
};

...

A an_a_object;
an_a_object.foo(5);
A::foo(&an_a_object, 5); // This is the same as the line above!
std::cout << an_a_object.data; // Prints 10!

Có bao nhiêu tham số A::foo? Thông thường chúng ta sẽ nói là 1. Nhưng dưới mui xe, foo thực sự chiếm 2. Nhìn vào định nghĩa của A :: foo, nó cần một trường hợp cụ thể của A để con trỏ 'this' có nghĩa (trình biên dịch cần biết những gì ' đây là). Cách bạn thường chỉ định những gì bạn muốn 'cái này' là thông qua cú pháp MyObject.MyMemberFunction(). Nhưng đây chỉ là đường cú pháp để chuyển địa chỉ của MyObjectlàm tham số đầu tiên tớiMyMemberFunction. Tương tự như vậy, khi chúng ta khai báo các hàm thành viên bên trong các định nghĩa lớp, chúng ta không đặt 'this' trong danh sách tham số, mà đây chỉ là một món quà từ các nhà thiết kế ngôn ngữ để tiết kiệm việc gõ. Thay vào đó, bạn phải chỉ định rằng một hàm thành viên là tĩnh để chọn không tham gia sẽ tự động nhận tham số 'this' bổ sung. Nếu trình biên dịch C ++ dịch ví dụ trên sang mã C (trình biên dịch C ++ gốc thực sự hoạt động theo cách đó), nó có thể sẽ viết một cái gì đó như thế này:

struct A {
    int data;
};

void a_init(A* to_init)
{
    to_init->data = 0;
}

void a_foo(A* this, int addToData)
{ 
    this->data += addToData;
}

...

A an_a_object;
a_init(0); // Before constructor call was implicit
a_foo(&an_a_object, 5); // Used to be an_a_object.foo(5);

Quay trở lại ví dụ của bạn, bây giờ có một vấn đề rõ ràng. 'Init' muốn một con trỏ đến một hàm nhận một tham số. Nhưng &CLoggersInfra::RedundencyManagerCallBacklà một con trỏ đến một hàm có hai tham số, đó là tham số bình thường và tham số bí mật 'this'. Đó là lý do tại sao bạn vẫn gặp lỗi trình biên dịch (lưu ý phụ: Nếu bạn đã từng sử dụng Python, loại nhầm lẫn này là lý do tại sao tham số 'self' là bắt buộc cho tất cả các hàm thành viên).

Cách dài dòng để xử lý điều này là tạo một đối tượng đặc biệt chứa một con trỏ đến cá thể bạn muốn và có một hàm thành viên được gọi là một cái gì đó như 'run' hoặc 'execute' (hoặc nạp chồng toán tử '()') nhận các tham số cho hàm thành viên, và chỉ cần gọi hàm thành viên với các tham số đó trên cá thể được lưu trữ. Nhưng điều này sẽ yêu cầu bạn thay đổi 'Init' để lấy đối tượng đặc biệt của bạn thay vì một con trỏ hàm thô, và có vẻ như Init là mã của người khác. Và việc tạo một lớp đặc biệt cho mỗi khi vấn đề này xuất hiện sẽ dẫn đến sự cồng kềnh mã.

Vì vậy, bây giờ, cuối cùng, giải pháp tốt boost::bindboost::function, tài liệu cho mỗi bạn có thể tìm thấy ở đây:

boost :: bind docs , boost :: function docs

boost::bindsẽ cho phép bạn lấy một hàm và một tham số cho hàm đó và tạo một hàm mới trong đó tham số đó được 'khóa' tại chỗ. Vì vậy, nếu tôi có một hàm thêm hai số nguyên, tôi có thể sử dụng boost::bindđể tạo một hàm mới trong đó một trong các tham số bị khóa là 5. Hàm mới này sẽ chỉ nhận một tham số nguyên và sẽ luôn thêm 5 cụ thể vào nó. Sử dụng kỹ thuật này, bạn có thể 'khóa' tham số ẩn 'this' để là một cá thể lớp cụ thể và tạo một hàm mới chỉ nhận một tham số, giống như bạn muốn (lưu ý rằng tham số ẩn luôn là tham số đầu tiên , và các thông số bình thường có thứ tự sau nó). Nhìn vàoboost::bindtài liệu cho các ví dụ, họ thậm chí còn thảo luận cụ thể về việc sử dụng nó cho các chức năng thành viên. Về mặt kỹ thuật, có một hàm tiêu chuẩn được gọi là [std::bind1st][3]bạn cũng có thể sử dụng, nhưng boost::bindtổng quát hơn.

Tất nhiên, chỉ còn một cách nữa. boost::bindsẽ tạo ra một hàm boost :: tốt cho bạn, nhưng về mặt kỹ thuật thì đây vẫn không phải là một con trỏ hàm thô như Init có lẽ muốn. Rất may, boost cung cấp một cách để chuyển đổi boost :: function thành con trỏ thô, như được ghi lại trên StackOverflow tại đây . Cách nó thực hiện điều này nằm ngoài phạm vi của câu trả lời này, mặc dù nó cũng thú vị.

Đừng lo lắng nếu điều này có vẻ khó nghe - câu hỏi của bạn giao nhau giữa một số góc tối của C ++ và boost::bindcực kỳ hữu ích khi bạn học nó.

Cập nhật C ++ 11: Thay vì boost::bindbây giờ bạn có thể sử dụng một hàm lambda để nắm bắt 'cái này'. Điều này về cơ bản là có trình biên dịch tạo ra cùng một thứ cho bạn.


2
Đây là một câu trả lời tuyệt vời!
aardvarkk,

5
Ở phần đầu của câu trả lời, std :: bind1st được đề xuất như một cách để triển khai giải pháp, nhưng phần sau của câu trả lời chỉ là về boost :: bind. Làm thế nào có thể sử dụng std :: bind1st?
mabraham

1
@mabraham Được rồi, tôi đã thêm một ví dụ nhanh mặc dù nó không hoàn toàn phù hợp với câu hỏi (VS2008): stackoverflow.com/a/45525074/4566599
Roi Danton

3
Đây phải là câu trả lời được chấp nhận! Câu trả lời được chấp nhận không thực sự hoạt động nếu bạn không thể thay đổi thư viện hoặc chuyển các nhóm tùy chọn.
Carson McNeil

1
@Joseph Garvin: Không biết câu trả lời là std :: bind như thế nào. điều này yêu cầu đối số phải có kiểu std :: function thay vì một con trỏ hàm C bình thường. chỉ vì bạn ẩn việc vượt qua điều này, nó không tốt hơn câu trả lời được chấp nhận. Chà, được rồi, nếu bạn có quyền truy cập cấp nguồn vào chữ ký hàm được đề cập, bạn có thể thay đổi foo * thành std :: function <foo_signature> và sau đó bạn chỉ cần thay đổi điều này, giả sử tất cả các trình biên dịch đã cập nhật lên C ++ 11 , nhưng nếu bạn không có quyền truy cập nguồn, thì bạn là F * ED, vì các chữ ký không tương thích. Điều này giống với biểu thức lambda trong C ++.
Stefan Steiger

21

Câu trả lời này là câu trả lời cho nhận xét ở trên và không hoạt động với VisualStudio 2008 nhưng sẽ được ưu tiên hơn với các trình biên dịch mới hơn.


Trong khi đó, bạn không phải sử dụng con trỏ void nữa và cũng không cần tăng cường kể từ khi std::bindstd::functioncó sẵn. Một lợi thế (so với con trỏ void) là kiểu an toàn vì kiểu trả về và các đối số được nêu rõ ràng bằng cách sử dụng std::function:

// std::function<return_type(list of argument_type(s))>
void Init(std::function<void(void)> f);

Sau đó, bạn có thể tạo con trỏ hàm std::bindvà chuyển nó vào Init:

auto cLoggersInfraInstance = CLoggersInfra();
auto callback = std::bind(&CLoggersInfra::RedundencyManagerCallBack, cLoggersInfraInstance);
Init(callback);

Ví dụ hoàn chỉnh để sử dụng std::bindvới các hàm thành viên, thành viên tĩnh và không phải là thành viên:

#include <functional>
#include <iostream>
#include <string>

class RedundencyManager // incl. Typo ;-)
{
public:
    // std::function<return_type(list of argument_type(s))>
    std::string Init(std::function<std::string(void)> f) 
    {
        return f();
    }
};

class CLoggersInfra
{
private:
    std::string member = "Hello from non static member callback!";

public:
    static std::string RedundencyManagerCallBack()
    {
        return "Hello from static member callback!";
    }

    std::string NonStaticRedundencyManagerCallBack()
    {
        return member;
    }
};

std::string NonMemberCallBack()
{
    return "Hello from non member function!";
}

int main()
{
    auto instance = RedundencyManager();

    auto callback1 = std::bind(&NonMemberCallBack);
    std::cout << instance.Init(callback1) << "\n";

    // Similar to non member function.
    auto callback2 = std::bind(&CLoggersInfra::RedundencyManagerCallBack);
    std::cout << instance.Init(callback2) << "\n";

    // Class instance is passed to std::bind as second argument.
    // (heed that I call the constructor of CLoggersInfra)
    auto callback3 = std::bind(&CLoggersInfra::NonStaticRedundencyManagerCallBack,
                               CLoggersInfra()); 
    std::cout << instance.Init(callback3) << "\n";
}

Đầu ra có thể:

Hello from non member function!
Hello from static member callback!
Hello from non static member callback!

Hơn nữa bằng cách sử dụng, std::placeholdersbạn có thể chuyển động các đối số cho lệnh gọi lại (ví dụ: điều này cho phép sử dụng return f("MyString");in Initif f có tham số chuỗi).


1
một thực sự, thực sự rất lớn cảm ơn bạn từ tôi cho câu trả lời này! Tôi đã dành hơn hai giờ để tìm kiếm và thử các cách tiếp cận khác nhau, không có gì thực sự hiệu quả. Nhưng cái này rất đơn giản, nó chỉ hoạt động sau 1 phút.
BadK

3

Lập luận nào Initxảy ra? Thông báo lỗi mới là gì?

Con trỏ phương thức trong C ++ hơi khó sử dụng. Bên cạnh chính con trỏ phương thức, bạn cũng cần cung cấp một con trỏ thể hiện (trong trường hợp của bạn this). Có thể Initmong đợi nó như một đối số riêng biệt?


3

Một con trỏ đến một hàm thành viên lớp không giống như một con trỏ đến một hàm. Một thành viên lớp nhận một đối số phụ ngầm định ( con trỏ this ) và sử dụng một quy ước gọi khác.

Nếu API của bạn mong đợi một hàm gọi lại không phải là bộ nhớ, đó là những gì bạn phải chuyển cho nó.


3

m_cRedundencyManagerthể sử dụng các chức năng thành viên không? Hầu hết các lệnh gọi lại được thiết lập để sử dụng các hàm thông thường hoặc các hàm thành viên tĩnh. Hãy xem trang này tại C ++ FAQ Lite để biết thêm thông tin.

Cập nhật: Tờ khai chức năng mà bạn cung cấp cho thấy m_cRedundencyManagerdự kiến một hàm có dạng: void yourCallbackFunction(int, void *). Do đó, các hàm thành viên không được chấp nhận như là lệnh gọi lại trong trường hợp này. Một hàm thành viên tĩnh có thể hoạt động, nhưng nếu điều đó là không thể chấp nhận được trong trường hợp của bạn, thì đoạn mã sau cũng sẽ hoạt động. Lưu ý rằng nó sử dụng một dàn diễn viên ác từ void *.


// in your CLoggersInfra constructor:
m_cRedundencyManager->Init(myRedundencyManagerCallBackHandler, this);

// in your CLoggersInfra header:
void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr);

// in your CLoggersInfra source file:
void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr)
{
    ((CLoggersInfra *)CLoggersInfraPtr)->RedundencyManagerCallBack(i);
}

3

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)

// A RGB image
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;
}

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>

// this is the closure function to be allocated 
void function (void* data, va_alist alist)
{
     int abc = va_arg_int(alist);

     printf("data: %08p\n", data); // hex 0x14 = 20
     printf("abc: %d\n", abc);

     // va_start_type(alist[, return_type]);
     // arg = va_arg_type(alist[, arg_type]);
     // va_return_type(alist[[, return_type], return_value]);

    // va_start_int(alist);
    // int r = 666;
    // va_return_int(alist, r);
}



int main(int argc, char* argv[])
{
    int in1 = 10;

    void * data = (void*) 20;
    void(*incrementer1)(int abc) = (void(*)()) alloc_callback(&function, data);
    // void(*incrementer1)() can have unlimited arguments, e.g. incrementer1(123,456);
    // void(*incrementer1)(int abc) starts to throw errors...
    incrementer1(123);
    // free_callback(callback);
    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.


1

Tôi có thể thấy rằng init có ghi đè sau:

Init(CALLBACK_FUNC_EX callback_func, void * callback_parm)

nơi CALLBACK_FUNC_EX

typedef void (*CALLBACK_FUNC_EX)(int, void *);

1

Một giải pháp đơn giản "cách giải quyết" vẫn là tạo một lớp các chức năng ảo "giao diện" và kế thừa nó trong lớp người gọi. Sau đó, chuyển nó như một tham số "có thể nằm trong hàm tạo" của lớp khác mà bạn muốn gọi lại lớp người gọi của mình.

Giao diện DEFINE:

class CallBack 
{
   virtual callMeBack () {};
};

Đây là lớp mà bạn muốn gọi lại cho bạn:

class AnotherClass ()
{
     public void RegisterMe(CallBack *callback)
     {
         m_callback = callback;
     }

     public void DoSomething ()
     {
        // DO STUFF
        // .....
        // then call
        if (m_callback) m_callback->callMeBack();
     }
     private CallBack *m_callback = NULL;
};

Và đây là lớp sẽ được gọi lại.

class Caller : public CallBack
{
    void DoSomthing ()
    {
    }

    void callMeBack()
    {
       std::cout << "I got your message" << std::endl;
    }
};

0

Câu hỏi và câu trả lời này từ C ++ FAQ Lite bao gồm câu hỏi của bạn và những cân nhắc liên quan đến câu trả lời mà tôi nghĩ khá hay. Đoạn mã ngắn từ trang web tôi đã liên kết:

Đừng.

Bởi vì một hàm thành viên là vô nghĩa nếu không có đối tượng để gọi nó lên, bạn không thể thực hiện điều này trực tiếp (nếu Hệ thống cửa sổ X được viết lại bằng C ++, nó có thể sẽ chuyển các tham chiếu đến các đối tượng xung quanh, không chỉ con trỏ đến các hàm; tự nhiên là các đối tượng sẽ thể hiện hàm được yêu cầu và có thể còn nhiều hơn thế nữa).


1
Liên kết hiện là isocpp.org/wiki/faq/pointers-to-members#memfnptr-vs-fnptr ; Có vẻ như bây giờ anh ấy nói "Đừng". Đây là lý do tại sao câu trả lời chỉ có liên kết là không tốt.
lmat - Khôi phục Monica

2
Tôi đã sửa đổi câu trả lời @LimitedAtonement. Cảm ơn vì đã chỉ ra điều này. Bạn hoàn toàn chính xác rằng câu trả lời chỉ liên kết là câu trả lời chất lượng thấp. Nhưng chúng ta không biết rằng trở lại trong năm 2008 :-P
Onorio Catenacci

0

Kiểu của con trỏ tới hàm thành viên không tĩnh khác với con trỏ tới hàm thông thường .
Loại là void(*)(int)nếu đó là một hàm thành viên bình thường hoặc tĩnh .
Loại là void(CLoggersInfra::*)(int)nếu đó là một hàm thành viên không tĩnh .
Vì vậy, bạn không thể chuyển một con trỏ đến một hàm thành viên không tĩnh nếu nó đang mong đợi một con trỏ hàm bình thường.

Hơn nữa, một hàm thành viên không tĩnh có một tham số ngầm / ẩn đối với đối tượng. Con thistrỏ được truyền ngầm như một đối số cho lời gọi hàm thành viên. Vì vậy, các hàm thành viên có thể được gọi chỉ bằng cách cung cấp một đối tượng.

Nếu Init không thể thay đổi API , một hàm bao bọc (hàm thông thường hoặc hàm thành viên tĩnh của lớp) gọi thành viên đó có thể được sử dụng. Trong trường hợp xấu nhất, đối tượng sẽ là toàn cục để hàm wrapper có thể truy cập.

CLoggersInfra* pLoggerInfra;

RedundencyManagerCallBackWrapper(int val)
{
    pLoggerInfra->RedundencyManagerCallBack(val);
}
m_cRedundencyManager->Init(RedundencyManagerCallBackWrapper);

Nếu API Init có thể được thay đổi, có nhiều lựa chọn thay thế - Con trỏ hàm thành viên không tĩnh đối tượng, Đối tượng Hàm std::functionhoặc Hàm Giao diện.

Xem bài đăng về các lệnh gọi lại để biết các biến thể khác nhau với các ví dụ làm việc C ++ .


0

Có vẻ như std::mem_fn(C ++ 11) thực hiện chính xác những gì bạn cần:

Mẫu hàm std :: mem_fn tạo ra các đối tượng bao bọc cho các con trỏ tới thành viên, có thể lưu trữ, sao chép và gọi một con trỏ tới thành viên. Cả hai tham chiếu và con trỏ (bao gồm cả con trỏ thông minh) đến một đối tượng đều có thể được sử dụng khi gọi một std :: mem_fn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.