Câu hỏi Làm thế nào tôi có thể vượt qua một chức năng thành viên, nơi một chức năng miễn phí dự kiến?


Câu hỏi đặt ra là: xem xét đoạn mã sau:

#include <iostream>


class aClass
{
public:
    void aTest(int a, int b)
    {
        printf("%d + %d = %d", a, b, a + b);
    }
};

void function1(void (*function)(int, int))
{
    function(1, 1);
}

void test(int a,int b)
{
    printf("%d - %d = %d", a , b , a - b);
}

int main (int argc, const char* argv[])
{
    aClass a();

    function1(&test);
    function1(&aClass::aTest); // <-- How should I point to a's aClass::test function?

    return 0;
}

Làm cách nào tôi có thể sử dụng a'S aClass::test như một đối số function1? Tôi đang mắc kẹt trong việc này.

Tôi muốn truy cập một thành viên của lớp.


76
2017-09-30 16:28


gốc


Hãy xem câu trả lời này stackoverflow.com/questions/2402579/…và cũng là C ++ parashift.com/c++-faq/pointers-to-members.html - amdn
Điều này hoàn toàn không phải là một bản sao (ít nhất là không phải của câu hỏi cụ thể được liên kết). Câu hỏi đó là về cách khai báo một thành viên là một con trỏ tới một hàm; đây là về cách chuyển một con trỏ tới một hàm thành viên không tĩnh như một tham số. - CarLuva


Các câu trả lời:


Không có gì sai khi sử dụng con trỏ hàm. Tuy nhiên, các con trỏ tới các hàm thành viên không tĩnh không giống như các con trỏ hàm bình thường: các hàm thành viên cần phải được gọi trên một đối tượng được chuyển như một đối số ngầm định cho hàm. Chữ ký của hàm thành viên của bạn ở trên là, do đó

void (aClass::*)(int, int)

thay vì loại bạn cố gắng sử dụng

void (*)(int, int)

Một cách tiếp cận có thể bao gồm trong việc thực hiện chức năng thành viên static trong trường hợp đó, nó không yêu cầu bất kỳ đối tượng nào được gọi và bạn có thể sử dụng nó với kiểu void (*)(int, int).

Nếu bạn cần truy cập bất kỳ thành viên không tĩnh nào trong lớp của bạn  bạn cần phải gắn với các con trỏ hàm, ví dụ, bởi vì hàm này là một phần của giao diện C, tùy chọn tốt nhất của bạn là luôn chuyển void* với chức năng của bạn lấy con trỏ hàm và gọi cho thành viên của bạn thông qua một chức năng chuyển tiếp mà có được một đối tượng từ void* và sau đó gọi hàm thành viên.

Trong một giao diện C ++ thích hợp, bạn có thể muốn xem xét việc có hàm của bạn lấy đối số templated cho các đối tượng hàm để sử dụng các kiểu lớp tùy ý. Nếu sử dụng giao diện templated là không mong muốn, bạn nên sử dụng một cái gì đó như std::function<void(int, int)>: bạn có thể tạo một đối tượng hàm có thể gọi phù hợp cho các đối tượng này, ví dụ: sử dụng std::bind().

Các phương pháp tiếp cận kiểu an toàn sử dụng một đối số mẫu cho loại lớp hoặc phù hợp std::function<...> thích hợp hơn là sử dụng void* giao diện khi họ loại bỏ khả năng xảy ra lỗi do truyền đến loại sai.

Để làm rõ cách sử dụng một con trỏ hàm để gọi hàm thành viên, đây là một ví dụ:

// the function using the function pointers:
void somefunction(void (*fptr)(void*, int, int), void* context) {
    fptr(context, 17, 42);
}

void non_member(void*, int i0, int i1) {
    std::cout << "I don't need any context! i0=" << i0 << " i1=" << i1 << "\n";
}

struct foo {
    void member(int i0, int i1) {
        std::cout << "member function: this=" << this << " i0=" << i0 << " i1=" << i1 << "\n";
    }
};

void forwarder(void* context, int i0, int i1) {
    static_cast<foo*>(context)->member(i0, i1);
}

int main() {
    somefunction(&non_member, 0);
    foo object;
    somefunction(&forwarder, &object);
}

92
2017-09-30 16:36



Ok, tôi thích câu trả lời này! Bạn có thể vui lòng chỉ định ý của bạn với "gọi thành viên của bạn thông qua một chức năng chuyển tiếp có được một đối tượng từ void * và sau đó gọi chức năng thành viên", hoặc chia sẻ một liên kết hữu ích với nó? Cảm ơn - Jorge Leitão
Tôi nghĩ rằng tôi đã nhận nó. (Tôi đã chỉnh sửa bài đăng của bạn) Cảm ơn bạn đã giải thích và ví dụ, thực sự hữu ích. Chỉ cần xác nhận: đối với mọi chức năng thành viên mà tôi muốn chỉ ra, tôi phải thực hiện giao nhận. Đúng? - Jorge Leitão
Vâng, vâng, loại. Tùy thuộc vào mức độ hiệu quả của việc sử dụng các mẫu, bạn có thể thoát khỏi việc tạo các mẫu chuyển tiếp có thể làm việc với các lớp khác nhau và các hàm thành viên. Làm thế nào để làm điều này sẽ là một chức năng riêng biệt, tôi nghĩ rằng ;-) - Dietmar Kühl
Tôi không thích câu trả lời này vì nó sử dụng void*, có nghĩa là bạn có thể gặp lỗi rất khó chịu, vì nó không được đánh máy kiểm tra nữa. - Superlokkus
@Superlokkus: bạn có thể vui lòng khai sáng cho chúng tôi bằng cách thay thế không? - Dietmar Kühl


@Pete Becker của câu trả lời là tốt nhưng bạn cũng có thể làm điều đó mà không vượt qua class ví dụ như một tham số rõ ràng function1 trong C ++ 11:

#include <functional>
using namespace std::placeholders;

void function1(std::function<void(int, int)> fun)
{
    fun(1, 1);
}

int main (int argc, const char * argv[])
{
   ...

   aClass a;
   auto fp = std::bind(&aClass::test, a, _1, _2);
   function1(fp);

   return 0;
}

54
2017-09-30 16:42



Là void function1(std::function<void(int, int)>) chính xác? - Deqing
Bạn cần cung cấp cho đối số hàm một tên biến và sau đó tên biến là những gì bạn thực sự vượt qua. Vì thế: void function1(std::function<void(int, int)> functionToCall) và sau đó functionToCall(1,1);. Tôi đã cố gắng chỉnh sửa câu trả lời nhưng ai đó từ chối nó như không có ý nghĩa gì vì một lý do nào đó. Chúng tôi sẽ xem liệu nó có được tăng giá hay không tại một thời điểm nào đó. - Dorky Engineer
@DorkyEngineer Đó là khá lạ, tôi nghĩ rằng bạn phải đúng nhưng tôi không biết làm thế nào mà lỗi có thể đã đi không được chú ý quá lâu. Dù sao, tôi đã chỉnh sửa câu trả lời ngay bây giờ. - Matt Phillips
tôi đã tìm thấy cái này bài đăng nói rằng có một hình phạt hiệu suất nghiêm trọng từ std :: function. - kevin


Một con trỏ tới hàm thành viên khác với một con trỏ trỏ đến hàm. Để sử dụng hàm thành viên thông qua một con trỏ, bạn cần một con trỏ đến nó (rõ ràng) và một đối tượng để áp dụng nó. Vì vậy, phiên bản thích hợp của function1 sẽ là

void function1(void (aClass::*function)(int, int), aClass& a) {
    (a.*function)(1, 1);
}

và gọi nó là:

aClass a; // note: no parentheses; with parentheses it's a function declaration
function1(&aClass::test, a);

37
2017-09-30 16:35



Cảm ơn nhiều. Tôi chỉ phát hiện ra rằng dấu ngoặc đếm ở đây: function1 (& (aClass :: test), a) hoạt động với MSVC2013 nhưng không phải với gcc. gcc cần & trực tiếp ở phía trước tên lớp (mà tôi thấy khó hiểu, bởi vì toán tử & sẽ lấy địa chỉ của hàm, không phải của lớp) - kritzel_sw
tôi đã nghĩ function trong void (aClass::*function)(int, int) là một loại, vì nó là một loại typedef void (aClass::*function)(int, int). - Olumide
@Olumide - typedef int X; định nghĩa một loại; int X; tạo một đối tượng. - Pete Becker


Từ năm 2011, nếu bạn có thể thay đổi function1, làm như vậy, như thế này:

#include <functional>
#include <cstdio>

using namespace std;

class aClass
{
public:
    void aTest(int a, int b)
    {
        printf("%d + %d = %d", a, b, a + b);
    }
};

template <typename Callable>
void function1(Callable f)
{
    f(1, 1);
}

void test(int a,int b)
{
    printf("%d - %d = %d", a , b , a - b);
}

int main()
{
    aClass obj;

    // Free function
    function1(&test);

    // Bound member function
    using namespace std::placeholders;
    function1(std::bind(&aClass::aTest, obj, _1, _2));

    // Lambda
    function1([&](int a, int b) {
        obj.aTest(a, b);
    });
}

(bản thử trực tiếp)

Cũng lưu ý rằng tôi đã sửa định nghĩa đối tượng bị hỏng của bạn (aClass a(); khai báo hàm).


0
2017-08-10 16:03





Bạn có thể ngừng đập đầu của bạn bây giờ. Đây là trình bao bọc cho hàm thành viên để hỗ trợ hiện tại chức năng lấy đồng bằng C các hàm làm đối số. thread_local chỉ thị là chìa khóa ở đây.

http://cpp.sh/9jhk3

// Example program
#include <iostream>
#include <string>

using namespace std;

typedef int FooCooker_ (int);

// Existing function
extern "C" void cook_10_foo (FooCooker_ FooCooker) {
    cout << "Cooking 10 Foo ..." << endl;
    cout << "FooCooker:" << endl;
    FooCooker (10);
}

struct Bar_ {
    Bar_ (int Foo = 0) : Foo (Foo) {};
    int cook (int Foo) {
        cout << "This Bar got " << this->Foo << endl;
        if (this->Foo >= Foo) {
            this->Foo -= Foo;
            cout << Foo << " cooked" << endl;
            return Foo;
        } else {
            cout << "Can't cook " <<  Foo << endl;
            return 0;
        }
    }
    int Foo = 0;
};

// Each Bar_ object and a member function need to define
// their own wrapper with a global thread_local object ptr
// to be called as a plain C function.
thread_local static Bar_* BarPtr = NULL;
static int cook_in_Bar (int Foo) {
    return BarPtr->cook (Foo);
}

thread_local static Bar_* Bar2Ptr = NULL;
static int cook_in_Bar2 (int Foo) {
    return Bar2Ptr->cook (Foo);
}

int main () {
  BarPtr = new Bar_ (20);
  cook_10_foo (cook_in_Bar);

  Bar2Ptr = new Bar_ (40);
  cook_10_foo (cook_in_Bar2);

  delete BarPtr;
  delete Bar2Ptr;
  return 0;
}

Hãy bình luận về bất kỳ vấn đề với cách tiếp cận này.

Các câu trả lời khác không gọi được hiện tại trơn C chức năng: http://cpp.sh/8exun


0
2017-08-07 10:24



Vì vậy, thay vì sử dụng std :: bind hoặc lambda để bọc cá thể bạn dựa vào một biến toàn cầu. Tôi không thể thấy bất kỳ lợi thế nào với phương pháp này so với các câu trả lời khác. - super
@super, Các câu trả lời khác không thể gọi các chức năng hiện có đang sử dụng đồng bằng C các hàm làm đối số. - neckTwi
Câu hỏi đặt ra là cách gọi hàm thành viên. Việc chuyển sang một hàm miễn phí đã làm việc cho OP trong câu hỏi. Bạn cũng không đi qua bất cứ điều gì. Chỉ có các hàm mã hóa cứng ở đây và một con trỏ Foo_ toàn cầu. Quy mô này sẽ như thế nào nếu bạn muốn gọi một hàm thành viên khác? Bạn sẽ phải viết lại các hàm bên dưới, hoặc sử dụng các hàm khác nhau cho mỗi mục tiêu. - super