เรียกใช้เมธอดคลาส C ++ ผ่านตัวชี้ฟังก์ชัน


113

ฉันจะรับฟังก์ชันพอยน์เตอร์สำหรับฟังก์ชันสมาชิกคลาสได้อย่างไรและในภายหลังเรียกฟังก์ชันสมาชิกนั้นด้วยอ็อบเจ็กต์เฉพาะ ฉันต้องการเขียน:

class Dog : Animal
{
    Dog ();
    void bark ();
}


Dog* pDog = new Dog ();
BarkFunction pBark = &Dog::bark;
(*pBark) (pDog);

นอกจากนี้ถ้าเป็นไปได้ฉันต้องการเรียกใช้ตัวสร้างผ่านตัวชี้ด้วย:

NewAnimalFunction pNew = &Dog::Dog;
Animal* pAnimal = (*pNew)();    

เป็นไปได้หรือไม่และถ้าเป็นเช่นนั้นวิธีใดที่ควรทำ


1
ฉันยังไม่เข้าใจจริงๆว่า 'ทำไม' ถ้าคุณต้องการเรียกใช้ฟังก์ชันสมาชิกของวัตถุเพียงแค่ส่งตัวชี้ไปยังวัตถุ? ถ้ามีคนบ่นว่าเพราะมันทำให้คุณสามารถห่อหุ้มคลาสได้ดีขึ้นทำไมไม่สร้างคลาสอินเตอร์เฟสที่คลาสทั้งหมดสืบทอดมา
ชาด

มันจะมีประโยชน์ในการใช้งานบางอย่างเช่นรูปแบบคำสั่งแม้ว่าหลาย ๆ คนจะใช้ฟังก์ชัน boost :: เพื่อซ่อนกลไกตัวชี้สมาชิกดิบ
CB Bailey

9
ทำไมคุณจัดสรรสุนัขตัวนั้นอย่างไม่หยุดนิ่ง จากนั้นคุณต้องลบวัตถุด้วยตนเองด้วย ดูเหมือนว่าคุณมาจาก Java, C # หรือภาษาอื่นที่เทียบเคียงได้และยังต่อสู้กับ C ++ วัตถุอัตโนมัติธรรมดา ( Dog dog;) เป็นไปได้มากกว่าที่คุณต้องการ
sbi

1
@ ชาด: ส่วนใหญ่ฉันเห็นด้วย แต่มีหลายครั้งที่การส่งเอกสารอ้างอิงจะมีค่าใช้จ่ายสูงกว่า พิจารณาลูปที่วนซ้ำอยู่เหนือข้อมูลบางประเภท (การแยกวิเคราะห์การคำนวณ ฯลฯ .. ) กว่าที่จะสามารถเรียกใช้ฟังก์ชันตามการคำนวณ if / else บางอย่างจะกำหนดค่าใช้จ่ายที่การเรียกใช้ฟังก์ชันที่ชี้เกินไปก็สามารถหลีกเลี่ยงสิ่งนั้นได้ / else ตรวจสอบว่าสามารถทำการตรวจสอบเหล่านี้ก่อนเข้าสู่ลูปได้หรือไม่
Eric

1
ยังเห็นตัวชี้ฟังก์ชั่นการทำงานของสมาชิก
jww

คำตอบ:


127

อ่านนี้เพื่อดูรายละเอียด:

// 1 define a function pointer and initialize to NULL

int (TMyClass::*pt2ConstMember)(float, char, char) const = NULL;

// C++

class TMyClass
{
public:
   int DoIt(float a, char b, char c){ cout << "TMyClass::DoIt"<< endl; return a+b+c;};
   int DoMore(float a, char b, char c) const
         { cout << "TMyClass::DoMore" << endl; return a-b+c; };

   /* more of TMyClass */
};
pt2ConstMember = &TMyClass::DoIt; // note: <pt2Member> may also legally point to &DoMore

// Calling Function using Function Pointer

(*this.*pt2ConstMember)(12, 'a', 'b');

24
น่าแปลกใจที่พวกเขาตัดสินใจว่าสิ่งนี้*this.*pt2Memberจะได้ผล *มีความสำคัญมากกว่า.*... โดยส่วนตัวแล้วฉันยังคงเขียนอยู่this->*pt2Memberนั่นคือตัวดำเนินการที่น้อยกว่า
Alexis Wilke

7
ทำไมคุณต้องเริ่มต้นpt2ConstMemberที่จะNULL?
Ciro Santilli 郝海东冠状病六四事件法轮功

@AlexisWilke ทำไมถึงแปลกใจ? สำหรับวัตถุโดยตรง (ไม่ใช่พอยน์เตอร์) (object.*method_pointer)ดังนั้นเราจึงต้องการ*ให้มีลำดับความสำคัญมากกว่า
Ciro Santilli 郝海东冠状病六四事件法轮功

@ TomášZatoถ้าฉันไม่เข้าใจผิด (และฉันอาจจะเป็น) thisก็แค่ถูกใช้เพื่อแสดงให้เห็นว่าสิ่งที่คุณสมัคร.*ควรเป็นตัวชี้ไปยังอินสแตนซ์ของคลาส (ย่อย) อย่างไรก็ตามนี่เป็นไวยากรณ์ใหม่สำหรับฉันฉันคาดเดาจากคำตอบและแหล่งข้อมูลอื่น ๆ ที่เชื่อมโยงที่นี่เท่านั้น ฉันกำลังแนะนำการแก้ไขเพื่อให้ชัดเจนยิ่งขึ้น
c1moore

1
ขอแสดงความยินดีกับ 100!
Jonathan Mee

59

ฉันจะรับฟังก์ชันพอยน์เตอร์สำหรับฟังก์ชันสมาชิกคลาสได้อย่างไรและในภายหลังเรียกฟังก์ชันสมาชิกนั้นด้วยอ็อบเจ็กต์เฉพาะ

เริ่มต้นด้วยไฟล์typedef. สำหรับฟังก์ชันสมาชิกคุณต้องเพิ่มชื่อคลาสในการประกาศประเภท:

typedef void(Dog::*BarkFunction)(void);

จากนั้นในการเรียกใช้เมธอดคุณใช้ตัว->*ดำเนินการ:

(pDog->*pBark)();

นอกจากนี้ถ้าเป็นไปได้ฉันต้องการเรียกใช้ตัวสร้างผ่านตัวชี้ด้วย เป็นไปได้หรือไม่และถ้าเป็นเช่นนั้นวิธีใดที่ควรทำ

ฉันไม่เชื่อว่าคุณจะทำงานกับคอนสตรัคเตอร์แบบนี้ได้ - ctors และ dtors นั้นพิเศษ วิธีปกติในการบรรลุสิ่งนั้นคือการใช้วิธีการโรงงานซึ่งโดยพื้นฐานแล้วเป็นเพียงฟังก์ชันคงที่ที่เรียกตัวสร้างให้คุณ ดูโค้ดด้านล่างสำหรับตัวอย่าง

ฉันได้แก้ไขโค้ดของคุณเพื่อทำตามที่คุณอธิบาย มีข้อแม้บางประการด้านล่าง

#include <iostream>

class Animal
{
public:

    typedef Animal*(*NewAnimalFunction)(void);

    virtual void makeNoise()
    {
        std::cout << "M00f!" << std::endl;
    }
};

class Dog : public Animal
{
public:

    typedef void(Dog::*BarkFunction)(void);

    typedef Dog*(*NewDogFunction)(void);

    Dog () {}

    static Dog* newDog()
    {
        return new Dog;
    }

    virtual void makeNoise ()
    {
        std::cout << "Woof!" << std::endl;
    }
};

int main(int argc, char* argv[])
{
    // Call member function via method pointer
    Dog* pDog = new Dog ();
    Dog::BarkFunction pBark = &Dog::makeNoise;

    (pDog->*pBark)();

    // Construct instance via factory method
    Dog::NewDogFunction pNew = &Dog::newDog;

    Animal* pAnimal = (*pNew)();

    pAnimal->makeNoise();

    return 0;
}

ในตอนนี้แม้ว่าคุณจะสามารถใช้ a Dog*แทนความAnimal*มหัศจรรย์ของความหลากหลายได้ แต่ประเภทของตัวชี้ฟังก์ชันจะไม่เป็นไปตามกฎการค้นหาของลำดับชั้นของคลาส ดังนั้นตัวชี้เมธอด Animal จึงไม่สามารถใช้งานร่วมกับตัวชี้วิธี Dog ได้กล่าวอีกนัยหนึ่งคือคุณไม่สามารถกำหนด a Dog* (*)()ให้กับตัวแปรประเภทAnimal* (*)()ได้

newDogวิธีคงที่เป็นตัวอย่างง่ายๆของโรงงานซึ่งสร้างและส่งคืนอินสแตนซ์ใหม่ ในฐานะที่เป็นฟังก์ชันคงที่มีปกติtypedef(โดยไม่มีคุณสมบัติของคลาส)

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

ที่เกี่ยวข้องกับข้างต้นคุณจะไม่ต้องสงสัยเลยว่าไลบรารีBoost bindและโมดูลอื่น ๆ ที่เกี่ยวข้องมีประโยชน์มาก


10
ฉันใช้ C ++ มานานกว่า 10 ปีและเรียนรู้สิ่งใหม่ ๆ อยู่เสมอ ฉันไม่เคยได้ยินมา->*ก่อน แต่ตอนนี้ฉันหวังว่าฉันจะไม่ต้องการมัน :)
โทมัส

31

ฉันไม่คิดว่าจะมีใครอธิบายที่นี่ว่าประเด็นหนึ่งคือคุณต้องการ " ตัวชี้สมาชิก " มากกว่าตัวชี้ฟังก์ชันปกติ

สมาชิกพอยน์เตอร์ถึงฟังก์ชันไม่ได้เป็นเพียงตัวชี้ฟังก์ชันเท่านั้น ในแง่การนำไปใช้งานคอมไพลเลอร์ไม่สามารถใช้ที่อยู่ฟังก์ชันธรรมดาได้เนื่องจากโดยทั่วไปคุณไม่ทราบที่อยู่ที่จะเรียกจนกว่าคุณจะทราบว่าจะยกเลิกการอ้างอิงวัตถุใด (คิดว่าฟังก์ชันเสมือนจริง) คุณต้องรู้วัตถุเพื่อที่จะระบุthisพารามิเตอร์โดยนัยแน่นอน

ต้องบอกว่าคุณต้องการตอนนี้ฉันจะบอกว่าคุณต้องหลีกเลี่ยงพวกเขาจริงๆ คำแนะนำสำหรับสมาชิกเป็นความเจ็บปวด มีเหตุผลมากกว่าที่จะดูรูปแบบการออกแบบเชิงวัตถุที่บรรลุเป้าหมายเดียวกันหรือใช้ a boost::functionหรืออะไรก็ได้ตามที่กล่าวไว้ข้างต้นโดยสมมติว่าคุณเลือกได้นั่นคือ

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

class myclass
{
  public:
    virtual void myrealmethod () = 0;

    static void myfunction (myclass *p);
}

void myclass::myfunction (myclass *p)
{
  p->myrealmethod ();
}

เนื่องจากmyfunctionเป็นเพียงฟังก์ชั่นปกติ (นอกเหนือจากปัญหาขอบเขต) ตัวชี้ฟังก์ชันสามารถพบได้ในวิธี C ปกติ

แก้ไข - วิธีการแบบนี้เรียกว่า "class method" หรือ "static member function" ความแตกต่างหลักจากฟังก์ชันที่ไม่ใช่สมาชิกคือหากคุณอ้างอิงจากภายนอกคลาสคุณต้องระบุขอบเขตโดยใช้ตัว::ดำเนินการแก้ไขขอบเขต ยกตัวอย่างเช่นการที่จะได้รับตัวชี้ฟังก์ชั่นการใช้งานและจะเรียกมันว่าใช้&myclass::myfunctionmyclass::myfunction (arg);

สิ่งนี้เป็นเรื่องธรรมดาเมื่อใช้ Win32 API แบบเก่าซึ่งเดิมออกแบบมาสำหรับ C มากกว่า C ++ แน่นอนในกรณีนั้นพารามิเตอร์ปกติคือ LPARAM หรือคล้ายกันแทนที่จะเป็นตัวชี้และจำเป็นต้องมีการหล่อ


'myfunction' ไม่ใช่ฟังก์ชันปกติถ้าโดยปกติคุณหมายถึงฟังก์ชันสไตล์ C 'myfunction' เรียกว่า method of myclass ได้ถูกต้องกว่า เมธอดของคลาสไม่เหมือนกับฟังก์ชันปกติตรงที่มีบางอย่างที่ฟังก์ชันสไตล์ C ไม่มีซึ่งเป็นตัวชี้ "นี้"
Eric

3
การแนะนำให้ใช้บูสต์เป็นเรื่องที่เข้มงวด มีเหตุผลที่ดีในการใช้ตัวชี้วิธีการ ฉันไม่รังเกียจที่จะพูดถึงการเพิ่มพลังเป็นทางเลือก แต่เกลียดเมื่อมีคนบอกว่าคนอื่นควรใช้มันโดยไม่ทราบข้อเท็จจริงทั้งหมด Boost มาในราคา! และหากนี่เป็นแพลตฟอร์มแบบฝังตัวก็อาจไม่ใช่ทางเลือกที่เป็นไปได้ นอกเหนือจากนี้ฉันชอบงานเขียนของคุณมาก
Eric

@Eric - ในประเด็นที่สองของคุณฉันไม่ได้ตั้งใจจะพูดว่า "คุณจะใช้ Boost" และอันที่จริงฉันไม่เคยใช้ Boost ด้วยตัวเองเลย ความตั้งใจ (เท่าที่ฉันรู้หลังจากผ่านไป 3 ปี) คือผู้คนควรมองหาทางเลือกอื่นและระบุความเป็นไปได้เล็กน้อย "หรืออะไรก็ได้" แสดงว่ารายการไม่ได้มีไว้เพื่อให้ครบถ้วนสมบูรณ์ ตัวชี้สมาชิกมีค่าใช้จ่ายในการอ่าน การแสดงแหล่งที่มาที่รัดกุมของพวกเขายังสามารถปิดบังต้นทุนรันไทม์ - โดยเฉพาะอย่างยิ่งตัวชี้สมาชิกไปยังเมธอดต้องรับมือกับทั้งวิธีที่ไม่ใช่เสมือนและเสมือนและต้องรู้ว่า
Steve314

@Eric - ไม่เพียงแค่นั้น แต่ปัญหาเหล่านี้ยังเป็นสาเหตุของการไม่สามารถพกพาได้ด้วยตัวชี้สมาชิก - Visual C ++ ในอดีตต้องการเบาะแสเพิ่มเติมเกี่ยวกับวิธีการแสดงประเภทตัวชี้ของสมาชิก ฉันจะใช้วิธีการทำงานแบบคงที่สำหรับระบบฝังตัว - การแสดงตัวชี้จะเหมือนกับตัวชี้ฟังก์ชันอื่น ๆ ค่าใช้จ่ายนั้นชัดเจนและไม่มีปัญหาในการพกพา และการเรียกที่ห่อหุ้มโดยฟังก์ชันสมาชิกแบบคงที่จะรู้ (ในเวลาคอมไพล์) ว่าการเรียกนั้นเป็นเสมือนหรือไม่ - ไม่จำเป็นต้องตรวจสอบเวลาทำงานนอกเหนือจากการค้นหา vtable ปกติสำหรับวิธีการเสมือน
Steve314

@Eric - ในจุดแรกของคุณ - ฉันทราบว่าฟังก์ชันสมาชิกคงที่ไม่เหมือนกับฟังก์ชัน C-style ทุกประการ (ด้วยเหตุนี้ "ขอบเขตปัญหากัน") แต่ฉันน่าจะรวมชื่อไว้ด้วย
Steve314

18
typedef void (Dog::*memfun)();
memfun doSomething = &Dog::bark;
....
(pDog->*doSomething)(); // if pDog is a pointer
// (pDog.*doSomething)(); // if pDog is a reference

2
ควรเป็น: (pDog -> * doSomething) (); // ถ้า pDog เป็นตัวชี้ // (pDog. * doSomething) (); // ถ้า pDog เป็นการอ้างอิงเป็นตัวดำเนินการ () มีลำดับความสำคัญสูงกว่า -> * และ. *
Tomek

12

ตัวอย่างที่รันได้น้อยที่สุด

main.cpp

#include <cassert>

class C {
    public:
        int i;
        C(int i) : i(i) {}
        int m(int j) { return this->i + j; }
};

int main() {
    // Get a method pointer.
    int (C::*p)(int) = &C::m;

    // Create a test object.
    C c(1);
    C *cp = &c;

    // Operator .*
    assert((c.*p)(2) == 3);

    // Operator ->*
    assert((cp->*p)(2) == 3);
}

รวบรวมและเรียกใช้:

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out

ทดสอบใน Ubuntu 18.04

คุณไม่สามารถเปลี่ยนลำดับของวงเล็บหรือละเว้นได้ สิ่งต่อไปนี้ใช้ไม่ได้:

c.*p(2)
c.*(p)(2)

GCC 9.2 จะล้มเหลวด้วย:

main.cpp: In function int main()’:
main.cpp:19:18: error: must use ‘.*’ or ‘->*’ to call pointer-to-member function in p (...)’, e.g. ‘(... ->* p) (...)’
   19 |     assert(c.*p(2) == 3);
      |

C ++ 11 มาตรฐาน

.*และ->*เป็นตัวดำเนินการเดียวที่นำมาใช้ใน C ++ เพื่อจุดประสงค์นี้และไม่มีอยู่ใน C

C ++ 11 N3337 ร่างมาตรฐาน :

  • 2.13 "ผู้ประกอบการและ punctuators" มีรายชื่อของผู้ประกอบการทั้งหมดซึ่งมีและ.*->*
  • 5.5 "ตัวดำเนินการชี้ถึงสมาชิก" อธิบายสิ่งที่พวกเขาทำ

11

ฉันมาที่นี่เพื่อเรียนรู้วิธีสร้างตัวชี้ฟังก์ชัน (ไม่ใช่ตัวชี้วิธี) จากวิธีการ แต่ไม่มีคำตอบใดที่ให้วิธีแก้ปัญหา นี่คือสิ่งที่ฉันคิดขึ้น:

template <class T> struct MethodHelper;
template <class C, class Ret, class... Args> struct MethodHelper<Ret (C::*)(Args...)> {
    using T = Ret (C::*)(Args...);
    template <T m> static Ret call(C* object, Args... args) {
        return (object->*m)(args...);
    }
};

#define METHOD_FP(m) MethodHelper<decltype(m)>::call<m>

ดังนั้นสำหรับตัวอย่างของคุณตอนนี้คุณต้องทำ:

Dog dog;
using BarkFunction = void (*)(Dog*);
BarkFunction bark = METHOD_FP(&Dog::bark);
(*bark)(&dog); // or simply bark(&dog)

แก้ไข:
การใช้ C ++ 17 มีวิธีแก้ปัญหาที่ดีกว่า:

template <auto m> struct MethodHelper;
template <class C, class Ret, class... Args, Ret (C::*m)(Args...)> struct MethodHelper<m> {
    static Ret call(C* object, Args... args) {
        return (object->*m)(args...);
    }
};

ซึ่งสามารถใช้ได้โดยตรงโดยไม่ต้องใช้มาโคร:

Dog dog;
using BarkFunction = void (*)(Dog*);
BarkFunction bark = MethodHelper<&Dog::bark>::call;
(*bark)(&dog); // or simply bark(&dog)

สำหรับวิธีการที่มีตัวปรับแต่งเช่นconstคุณอาจต้องการความเชี่ยวชาญเพิ่มเติมเช่น:

template <class C, class Ret, class... Args, Ret (C::*m)(Args...) const> struct MethodHelper<m> {
    static Ret call(const C* object, Args... args) {
        return (object->*m)(args...);
    }
};

6

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

ในการเรียกใช้ฟังก์ชันสมาชิกคุณจำเป็นต้องรู้สองสิ่ง:

  • ฟังก์ชั่นสมาชิกที่จะเรียกใช้
  • ควรใช้อินสแตนซ์ใด (ซึ่งมีฟังก์ชันสมาชิก)

พอยน์เตอร์ฟังก์ชันธรรมดาไม่สามารถจัดเก็บทั้งสองอย่างได้ พอยน์เตอร์ฟังก์ชันสมาชิก C ++ ใช้เพื่อจัดเก็บ a) ซึ่งเป็นสาเหตุที่คุณต้องระบุอินสแตนซ์อย่างชัดเจนเมื่อเรียกใช้ตัวชี้ฟังก์ชันสมาชิก


1
ฉันโหวตสิ่งนี้ แต่จะเพิ่มจุดชี้แจงในกรณีที่ OP ไม่ทราบว่าคุณหมายถึงอะไรโดย "อินสแตนซ์ใด" ฉันจะขยายเพื่ออธิบายตัวชี้ 'นี้' โดยธรรมชาติ
Eric

6

ตัวชี้ฟังก์ชันไปยังสมาชิกคลาสเป็นปัญหาที่เหมาะกับการใช้ฟังก์ชัน boost :: ตัวอย่างเล็ก ๆ :

#include <boost/function.hpp>
#include <iostream>

class Dog 
{
public:
   Dog (int i) : tmp(i) {}
   void bark ()
   {
      std::cout << "woof: " << tmp << std::endl;
   }
private:
   int tmp;
};



int main()
{
   Dog* pDog1 = new Dog (1);
   Dog* pDog2 = new Dog (2);

   //BarkFunction pBark = &Dog::bark;
   boost::function<void (Dog*)> f1 = &Dog::bark;

   f1(pDog1);
   f1(pDog2);
}

2

ในการสร้างวัตถุใหม่คุณสามารถใช้การวางตำแหน่งใหม่ดังที่กล่าวไว้ข้างต้นหรือให้คลาสของคุณใช้วิธีการ clone () ที่สร้างสำเนาของวัตถุ จากนั้นคุณสามารถเรียกวิธีการโคลนนี้โดยใช้ตัวชี้ฟังก์ชันสมาชิกตามที่อธิบายไว้ข้างต้นเพื่อสร้างอินสแตนซ์ใหม่ของวัตถุ ข้อดีของการโคลนคือบางครั้งคุณอาจทำงานโดยใช้ตัวชี้ไปยังคลาสฐานที่คุณไม่ทราบประเภทของวัตถุ ในกรณีนี้สามารถใช้วิธี clone () ได้ง่ายกว่า นอกจากนี้ clone () จะให้คุณคัดลอกสถานะของวัตถุหากนั่นคือสิ่งที่คุณต้องการ


โคลนอาจมีราคาแพงและ OP อาจต้องการหลีกเลี่ยงหากประสิทธิภาพเป็นปัญหาหรือมีข้อกังวลบางอย่าง
Eric

0

ฉันทำสิ่งนี้ด้วย std :: function และ std :: bind ..

ฉันเขียนคลาส EventManager นี้ซึ่งเก็บเวกเตอร์ของตัวจัดการใน unordered_map ที่แมปประเภทเหตุการณ์ (ซึ่งเป็นเพียง int ที่ไม่ได้ลงนามฉันมี enum ขนาดใหญ่ที่กำหนดขอบเขตเนมสเปซ) ไปยังเวกเตอร์ของตัวจัดการสำหรับประเภทเหตุการณ์นั้น

ในคลาส EventManagerTests ของฉันฉันตั้งค่าตัวจัดการเหตุการณ์ดังนี้:

auto delegate = std::bind(&EventManagerTests::OnKeyDown, this, std::placeholders::_1);
event_manager.AddEventListener(kEventKeyDown, delegate);

นี่คือฟังก์ชัน AddEventListener:

std::vector<EventHandler>::iterator EventManager::AddEventListener(EventType _event_type, EventHandler _handler)
{
    if (listeners_.count(_event_type) == 0) 
    {
        listeners_.emplace(_event_type, new std::vector<EventHandler>());
    }
    std::vector<EventHandler>::iterator it = listeners_[_event_type]->end();
    listeners_[_event_type]->push_back(_handler);       
    return it;
}

นี่คือคำจำกัดความประเภท EventHandler:

typedef std::function<void(Event *)> EventHandler;

จากนั้นกลับไปที่ EventManagerTests :: RaiseEvent ฉันทำสิ่งนี้:

Engine::KeyDownEvent event(39);
event_manager.RaiseEvent(1, (Engine::Event*) & event);

นี่คือรหัสสำหรับ EventManager :: RaiseEvent:

void EventManager::RaiseEvent(EventType _event_type, Event * _event)
{
    if (listeners_.count(_event_type) > 0)
    {
        std::vector<EventHandler> * vec = listeners_[_event_type];
        std::for_each(
            begin(*vec), 
            end(*vec), 
            [_event](EventHandler handler) mutable 
            {
                (handler)(_event);
            }
        );
    }
}

นี้ได้ผล ฉันได้รับสายใน EventManagerTests :: OnKeyDown ฉันต้องลบเวกเตอร์มาทำความสะอาดเวลา แต่เมื่อฉันทำแล้วไม่มีการรั่วไหล การเพิ่มเหตุการณ์จะใช้เวลาประมาณ 5 ไมโครวินาทีบนคอมพิวเตอร์ของฉันซึ่งประมาณปี 2008 ไม่ได้เร็วสุด ๆ แต่. พอใช้ตราบเท่าที่ฉันรู้และฉันไม่ได้ใช้มันในรหัสร้อนพิเศษ

ฉันต้องการเร่งความเร็วโดยการหมุน std :: function และ std :: bind ของตัวเองและอาจใช้อาร์เรย์อาร์เรย์แทน unordered_map ของเวกเตอร์ แต่ฉันยังไม่ได้หาวิธีจัดเก็บฟังก์ชันสมาชิก ตัวชี้และเรียกจากรหัสที่ไม่รู้อะไรเลยเกี่ยวกับคลาสที่ถูกเรียก คำตอบของขนตาดูน่าสนใจมาก ..

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