ฉันจะส่งผ่านฟังก์ชันสมาชิกที่คาดว่าจะมีฟังก์ชันฟรีได้อย่างไร


122

คำถามมีดังต่อไปนี้: พิจารณาโค้ดส่วนนี้:

#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;
}

ฉันจะใช้a's aClass::testเป็นอาร์กิวเมนต์ได้function1อย่างไร? ฉันติดอยู่ในการทำสิ่งนี้

ฉันต้องการเข้าถึงสมาชิกของชั้นเรียน


1
ดูคำตอบนี้stackoverflow.com/questions/2402579/…และคำถามที่พบบ่อย C ++ parashift.com/c++-faq/pointers-to-members.html
amdn

16
สิ่งนี้ไม่ซ้ำกันอย่างแน่นอน (อย่างน้อยก็ไม่ใช่คำถามเฉพาะที่เชื่อมโยงกัน) คำถามนั้นเกี่ยวกับวิธีการประกาศสมาชิกที่เป็นตัวชี้ไปยังฟังก์ชัน นี่คือวิธีการส่งตัวชี้ไปยังฟังก์ชันสมาชิกที่ไม่คงที่เป็นพารามิเตอร์
CarLuva

คำตอบ:


144

ไม่มีอะไรผิดปกติกับการใช้ตัวชี้ฟังก์ชัน อย่างไรก็ตามพอยน์เตอร์ไปยังฟังก์ชันสมาชิกที่ไม่คงที่ไม่เหมือนกับฟังก์ชันพอยน์เตอร์ปกติ: ฟังก์ชันสมาชิกจะต้องถูกเรียกใช้บนอ็อบเจ็กต์ซึ่งส่งผ่านเป็นอาร์กิวเมนต์โดยนัยไปยังฟังก์ชัน ลายเซ็นของฟังก์ชันสมาชิกของคุณข้างต้นจึงเป็นเช่นนั้น

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

มากกว่าประเภทที่คุณพยายามใช้

void (*)(int, int)

แนวทางหนึ่งอาจประกอบไปด้วยการสร้างฟังก์ชันสมาชิกstaticซึ่งในกรณีนี้ไม่จำเป็นต้องเรียกใช้อ็อบเจ็กต์ใด ๆ และคุณสามารถใช้กับประเภทvoid (*)(int, int)ได้

หากคุณต้องการเข้าถึงสมาชิกที่ไม่คงที่ในคลาสของคุณ และคุณจำเป็นต้องยึดติดกับตัวชี้ฟังก์ชันเช่นเนื่องจากฟังก์ชันนี้เป็นส่วนหนึ่งของอินเทอร์เฟซ C ตัวเลือกที่ดีที่สุดของคุณคือส่งต่อvoid*ไปยังฟังก์ชันโดยใช้ตัวชี้ฟังก์ชันและเรียกใช้ สมาชิกของคุณผ่านฟังก์ชั่นการส่งต่อซึ่งรับวัตถุจากvoid*แล้วเรียกใช้ฟังก์ชันสมาชิก

ในอินเทอร์เฟซ C ++ ที่เหมาะสมคุณอาจต้องการดูว่าฟังก์ชันของคุณใช้อาร์กิวเมนต์ที่เป็นเทมเพลตสำหรับอ็อบเจ็กต์ฟังก์ชันเพื่อใช้ประเภทคลาสโดยพลการ หากมีการใช้อินเตอร์เฟซที่เป็นที่พึงปรารถนาเทมเพลตที่คุณควรจะใช้สิ่งที่ต้องการstd::function<void(int, int)>: คุณสามารถสร้างวัตถุฟังก์ชั่น callable std::bind()เหมาะสมสำหรับเหล่านี้เช่นการใช้

วิธีการประเภทปลอดภัยโดยใช้อาร์กิวเมนต์แม่แบบสำหรับประเภทคลาสหรือเหมาะสมstd::function<...>จะดีกว่าการใช้void*อินเทอร์เฟซเนื่องจากจะลบโอกาสที่จะเกิดข้อผิดพลาดเนื่องจากการแคสต์ผิดประเภท

เพื่อชี้แจงวิธีใช้ตัวชี้ฟังก์ชันเพื่อเรียกใช้ฟังก์ชันสมาชิกนี่คือตัวอย่าง:

// 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);
}

โอเคฉันชอบคำตอบนี้! คุณช่วยระบุความหมายของคุณด้วย "โทรหาสมาชิกของคุณผ่านฟังก์ชันการส่งต่อซึ่งได้รับวัตถุจากโมฆะ * แล้วเรียกฟังก์ชันสมาชิก" หรือแชร์ลิงก์ที่เป็นประโยชน์ ขอบคุณ
Jorge Leitao

ฉันคิดว่าฉันเข้าใจแล้ว (ฉันแก้ไขโพสต์ของคุณแล้ว) ขอบคุณสำหรับคำอธิบายและตัวอย่างเป็นประโยชน์จริงๆ เพียงเพื่อยืนยัน: สำหรับทุกฟังก์ชั่นของสมาชิกที่ฉันต้องการชี้ฉันต้องทำการส่งต่อ ขวา?
Jorge Leitao

อืมใช่ชนิดของ ขึ้นอยู่กับว่าคุณใช้เทมเพลตได้อย่างมีประสิทธิภาพเพียงใดคุณสามารถสร้างเทมเพลตการส่งต่อซึ่งสามารถทำงานกับคลาสและฟังก์ชันสมาชิกที่แตกต่างกันได้ วิธีการทำสิ่งนี้จะเป็นหน้าที่แยกกันฉันคิดว่า ;-)
Dietmar Kühl

1
ฉันไม่ชอบคำตอบนี้เพราะมันใช้void*ซึ่งหมายความว่าคุณจะได้รับข้อบกพร่องที่น่ารังเกียจมากเนื่องจากไม่ได้พิมพ์ตรวจสอบอีกต่อไป
Superlokkus

@Superlokkus: คุณช่วยสอนเราด้วยทางเลือกอื่นได้ไหม?
Dietmar Kühl

88

คำตอบของ @Pete Becker นั้นใช้ได้ แต่คุณสามารถทำได้โดยไม่ต้องส่งclassอินสแตนซ์เป็นพารามิเตอร์ที่ชัดเจนfunction1ใน 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;
}

1
คือvoid function1(std::function<void(int, int)>)ถูกต้องหรือไม่
Deqing

2
คุณต้องตั้งชื่อตัวแปรให้กับอาร์กิวเมนต์ของฟังก์ชันจากนั้นชื่อตัวแปรคือสิ่งที่คุณส่งผ่าน ดังนั้น: แล้วvoid function1(std::function<void(int, int)> functionToCall) functionToCall(1,1);ฉันพยายามแก้ไขคำตอบ แต่มีคนปฏิเสธว่าไม่สมเหตุสมผลด้วยเหตุผลบางประการ เราจะดูว่ามีการโหวตเพิ่มขึ้นหรือไม่ในบางจุด
Dorky Engineer

1
@DorkyEngineer มันแปลกมากฉันคิดว่าคุณต้องถูก แต่ฉันไม่รู้ว่าข้อผิดพลาดนั้นจะไม่มีใครสังเกตเห็นมานานได้อย่างไร อย่างไรก็ตามฉันได้แก้ไขคำตอบแล้ว
Matt Phillips

2
ฉันพบว่าโพสต์นี้บอกว่ามีโทษประสิทธิภาพที่รุนแรงจากฟังก์ชัน std ::
kevin

2
@kevin คุณอาจต้องการแก้ไขความคิดเห็นของคุณเนื่องจากคำตอบของโพสต์นั้นมีข้อบกพร่องในเกณฑ์มาตรฐาน
Jorge Leitao

54

ฟังก์ชันตัวชี้ไปยังสมาชิกจะแตกต่างจากฟังก์ชันตัวชี้ ในการใช้ฟังก์ชันสมาชิกผ่านตัวชี้คุณต้องมีตัวชี้ไปที่มัน (ชัดเจน) และวัตถุที่จะนำไปใช้ ดังนั้นเวอร์ชันที่เหมาะสมfunction1จะเป็น

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

และเรียกมันว่า:

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

1
ขอบคุณมาก. ฉันเพิ่งค้นพบว่าวงเล็บนับที่นี่: function1 (& (aClass :: test), a) ทำงานกับ MSVC2013 แต่ไม่ใช่กับ gcc gcc ต้องการ & ตรงหน้าชื่อคลาส (ซึ่งฉันพบว่าสับสนเพราะตัวดำเนินการ & ใช้ที่อยู่ของฟังก์ชันไม่ใช่ของคลาส)
kritzel_sw

ผมคิดว่าfunctionในเป็นชนิดเพราะมันเป็นชนิดในvoid (aClass::*function)(int, int) typedef void (aClass::*function)(int, int)
Olumide

@Olumide - typedef int X;กำหนดประเภท int X;สร้างวัตถุ
Pete Becker

11

ตั้งแต่ปี 2011 หากคุณสามารถเปลี่ยนแปลงได้function1ให้ดำเนินการดังนี้:

#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);
    });
}

( การสาธิตสด )

โปรดสังเกตด้วยว่าฉันได้แก้ไขนิยามวัตถุเสียของคุณแล้ว ( aClass a();ประกาศฟังก์ชัน)


2

ฉันถามคำถามที่คล้ายกัน ( C ++ openframeworks ผ่านโมฆะจากคลาสอื่น ๆ ) แต่คำตอบที่ฉันพบนั้นชัดเจนกว่าดังนั้นคำอธิบายสำหรับบันทึกในอนาคต:

มันง่ายกว่าที่จะใช้ฟังก์ชัน std :: ใน:

 void draw(int grid, std::function<void()> element)

แล้วเรียกเป็น:

 grid.draw(12, std::bind(&BarrettaClass::draw, a, std::placeholders::_1));

หรือง่ายกว่านั้น:

  grid.draw(12, [&]{a.draw()});

ที่คุณสร้างแลมบ์ดาที่เรียกวัตถุที่จับโดยการอ้างอิง


1
ระบุว่านี่คือแท็ก CPP ฉันคิดว่านี่เป็นทางออกที่สะอาดที่สุด เราสามารถใช้การอ้างอิงอย่างต่อเนื่องเพื่อstd::functionในdrawแม้ว่าแทนการคัดลอกทุกเบอร์
Sohaib

2
ไม่ควรใช้ตัวยึดตำแหน่งในตัวอย่างนี้เนื่องจากฟังก์ชันไม่ใช้พารามิเตอร์ใด ๆ
kroiz

1

ฉันทำให้ฟังก์ชันสมาชิกเป็นแบบคงที่และทำงานได้ทั้งหมด:

#include <iostream>

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

void function1(int a,int b,void function(int, int))
{
    function(a, b);
}

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

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

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

    getchar();return 0;
}

ไม่เพียง แต่แก้คำถามหลัก "ฉันจะใช้ a's aClass :: test เป็นอาร์กิวเมนต์ของ function1 ได้อย่างไร" แต่ขอแนะนำให้หลีกเลี่ยงการแก้ไขตัวแปรส่วนตัวของคลาสโดยใช้โค้ดภายนอกของคลาส
mathengineer

1

ไม่แน่ใจว่าเหตุใดจึงมีการส่งต่อโซลูชันที่เรียบง่ายอย่างไม่น่าเชื่อนี้:

#include <stdio.h>

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

template<class C>
void function1(void (C::*function)(int, int), C& c)
{
    (c.*function)(1, 1);
}
void function1(void (*function)(int, int)) {
  function(1, 1);
}

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

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

    function1(&test);
    function1<aClass>(&aClass::aTest, a);
    return 0;
}

เอาท์พุท:

1 - 1 = 0
1 + 1 = 2

0

หากคุณไม่จำเป็นต้องใช้อินสแตนซ์a (เช่นคุณสามารถทำให้มันคงที่เหมือนคำตอบของ @mathengineer ) คุณก็สามารถส่งผ่านแลมด้าที่ไม่จับภาพได้ (ซึ่งสลายตัวเป็นตัวชี้ฟังก์ชัน)


#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);
}

int main()
{
   //note: you don't need the `+`
   function1(+[](int a,int b){return aClass{}.aTest(a,b);}); 
}

Wandbox


หมายเหตุ: หากaClassมีค่าใช้จ่ายสูงในการสร้างหรือมีผลข้างเคียงนี่อาจไม่ใช่วิธีที่ดี


-1

หยุดกระแทกหัวได้แล้ว นี่คือ wrapper สำหรับฟังก์ชันสมาชิกเพื่อรองรับฟังก์ชันที่มีอยู่โดยใช้ฟังก์ชันCธรรมดาเป็นอาร์กิวเมนต์ thread_localคำสั่งเป็นกุญแจสำคัญที่นี่

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;
}

โปรดแสดงความคิดเห็นเกี่ยวกับปัญหาใด ๆ เกี่ยวกับแนวทางนี้

คำตอบอื่น ๆ ไม่สามารถเรียกใช้ฟังก์ชันธรรมดาที่มีอยู่C : http://cpp.sh/8exun


3
ดังนั้นแทนที่จะใช้ std :: bind หรือ lambda เพื่อรวมอินสแตนซ์คุณต้องพึ่งพาตัวแปรส่วนกลาง ฉันไม่เห็นข้อได้เปรียบใด ๆ กับแนวทางนี้เมื่อเทียบกับคำตอบอื่น ๆ
ซุปเปอร์

@super คำตอบอื่น ๆ ไม่สามารถเรียกฟังก์ชันที่มีอยู่โดยใช้Cฟังก์ชันธรรมดาเป็นอาร์กิวเมนต์ได้
Necktwi

2
คำถามคือวิธีเรียกใช้ฟังก์ชันสมาชิก การส่งผ่านฟังก์ชั่นฟรีใช้งานได้กับ OP ในคำถามแล้ว คุณยังไม่ผ่านอะไรเลย มีเพียงฟังก์ชั่นฮาร์ดโค้ดที่นี่และตัวชี้ Foo_ ส่วนกลาง จะปรับขนาดนี้อย่างไรหากคุณต้องการเรียกใช้ฟังก์ชันสมาชิกอื่น คุณจะต้องเขียนฟังก์ชันพื้นฐานใหม่หรือใช้ฟังก์ชันที่แตกต่างกันสำหรับแต่ละเป้าหมาย
ซุปเปอร์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.