ผู้รับมอบสิทธิ์ C ++ คืออะไร


147

แนวคิดทั่วไปของผู้รับมอบสิทธิ์ใน C ++ คืออะไร? พวกเขาคืออะไรพวกเขาจะใช้อย่างไรและพวกเขาจะใช้เพื่ออะไร

ฉันต้องการเรียนรู้เกี่ยวกับพวกเขาเป็นครั้งแรกในแบบ 'กล่องดำ' แต่ข้อมูลเล็กน้อยเกี่ยวกับความกล้าหาญของสิ่งเหล่านี้ก็จะดีเช่นกัน

นี่ไม่ใช่ C ++ ที่บริสุทธิ์ที่สุดหรือสะอาดที่สุด แต่ฉันสังเกตว่า codebase ที่ฉันใช้มีมากมาย ฉันหวังว่าจะเข้าใจพวกเขามากพอดังนั้นฉันจึงสามารถใช้พวกเขาได้และไม่ต้องเจาะลึกถึงแม่แบบเทมเพลตที่น่ากลัว

บทความThe Code Projectทั้งสองนี้อธิบายสิ่งที่ฉันหมายถึง แต่ไม่ชัดเจนโดยเฉพาะ:


4
คุณกำลังพูดถึงการจัดการ C ++ ภายใต้. NET?
dasblinkenlight


5
delegateไม่ใช่ชื่อสามัญใน c ++ parlance คุณควรเพิ่มข้อมูลลงในคำถามเพื่อรวมบริบทที่คุณได้อ่าน โปรดทราบว่าในขณะที่รูปแบบอาจจะมีร่วมกันคำตอบอาจแตกต่างกันถ้าคุณพูดคุยเกี่ยวกับผู้ร่วมประชุมทั่วไปหรือในบริบทของ C ++ CLI หรือห้องสมุดอื่น ๆ ที่มีการดำเนินการโดยเฉพาะอย่างยิ่งของผู้ร่วมประชุม
David Rodríguez - dribeas

6
รอ 2 ปี?;)
อันดับ 1

6
ยังคงรอ ...
กริมม์โอดิเนอร์

คำตอบ:


173

คุณมีตัวเลือกมากมายอย่างไม่น่าเชื่อเพื่อให้ได้ผู้แทนใน C ++ นี่คือสิ่งที่อยู่ในใจของฉัน


ตัวเลือก 1: functors:

อาจมีการสร้างวัตถุฟังก์ชันโดยการนำไปใช้ operator()

struct Functor
{
     // Normal class/struct members

     int operator()(double d) // Arbitrary return types and parameter list
     {
          return (int) d + 1;
     }
};

// Use:
Functor f;
int i = f(3.14);

ตัวเลือกที่ 2: การแสดงออกแลมบ์ดา ( C ++ 11เท่านั้น)

// Syntax is roughly: [capture](parameter list) -> return type {block}
// Some shortcuts exist
auto func = [](int i) -> double { return 2*i/1.15; };
double d = func(1);

ตัวเลือก 3: พอยน์เตอร์ของฟังก์ชัน

int f(double d) { ... }
typedef int (*MyFuncT) (double d);
MyFuncT fp = &f;
int a = fp(3.14);

ตัวเลือก 4: ตัวชี้ไปยังฟังก์ชันสมาชิก (วิธีที่เร็วที่สุด)

ดูFast C ++ Delegate (ในThe Code Project )

struct DelegateList
{
     int f1(double d) { }
     int f2(double d) { }
};

typedef int (DelegateList::* DelegateType)(double d);

DelegateType d = &DelegateList::f1;
DelegateList list;
int a = (list.*d)(3.14);

ตัวเลือกที่ 5: ฟังก์ชันมาตรฐาน ::

(หรือboost::functionถ้าห้องสมุดมาตรฐานของคุณไม่รองรับ) มันช้ากว่า แต่ก็ยืดหยุ่นที่สุด

#include <functional>
std::function<int(double)> f = [can be set to about anything in this answer]
// Usually more useful as a parameter to another functions

ตัวเลือก 6: การรวม (ใช้std :: bind )

อนุญาตให้ตั้งค่าพารามิเตอร์บางอย่างล่วงหน้าสะดวกในการเรียกใช้ฟังก์ชันสมาชิกเช่น

struct MyClass
{
    int DoStuff(double d); // actually a DoStuff(MyClass* this, double d)
};

std::function<int(double d)> f = std::bind(&MyClass::DoStuff, this, std::placeholders::_1);
// auto f = std::bind(...); in C++11

ตัวเลือก 7: เทมเพลต

ยอมรับทุกสิ่งตราบใดที่ตรงกับรายการอาร์กิวเมนต์

template <class FunctionT>
int DoSomething(FunctionT func)
{
    return func(3.14);
}

2
รายการที่ยอดเยี่ยม +1 อย่างไรก็ตามมีเพียงสองคนเท่านั้นที่นับว่าเป็นผู้ได้รับมอบหมายที่นี่ - จับ lambdas และวัตถุที่ส่งคืนจากstd::bindและทั้งสองทำสิ่งเดียวกันยกเว้น lambdas ไม่ใช่ polymorphic ในแง่ที่ว่าพวกเขาสามารถยอมรับประเภทอาร์กิวเมนต์ที่แตกต่างกัน
Xeo

1
@JN: ทำไมคุณแนะนำไม่ให้ใช้ตัวชี้ฟังก์ชั่น แต่ดูเหมือนจะไม่เป็นไรกับการใช้ตัวชี้วิธีการสมาชิก? พวกเขาเหมือนกัน!
Matthieu M.

1
@MatthieuM : จุดยุติธรรม ฉันกำลังพิจารณาตัวชี้ฟังก์ชั่นเพื่อให้เป็นมรดก แต่นั่นอาจเป็นเพียงรสนิยมส่วนตัวของฉัน
JN

1
@ Xeo: ความคิดของฉันของผู้ได้รับมอบหมายค่อนข้างประจักษ์ บางทีฉันอาจผสมฟังก์ชั่นวัตถุและผู้ร่วมงานมากเกินไป (ตำหนิประสบการณ์ C # เก่าของฉัน)
JN

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

37

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

นี่คือตัวอย่าง:

template <class T>
class CCallback
{
public:
    typedef void (T::*fn)( int anArg );

    CCallback(T& trg, fn op)
        : m_rTarget(trg)
        , m_Operation(op)
    {
    }

    void Execute( int in )
    {
        (m_rTarget.*m_Operation)( in );
    }

private:

    CCallback();
    CCallback( const CCallback& );

    T& m_rTarget;
    fn m_Operation;

};

class A
{
public:
    virtual void Fn( int i )
    {
    }
};


int main( int /*argc*/, char * /*argv*/ )
{
    A a;
    CCallback<A> cbk( a, &A::Fn );
    cbk.Execute( 3 );
}

ซึ่งให้วิธีการเรียกใช้หรือไม่ ตัวแทน? อย่างไร ตัวชี้ฟังก์ชั่น?
SirYakalot

คลาสของผู้รับมอบสิทธิ์จะเสนอวิธีการExecute()ที่เรียกการเรียกใช้ฟังก์ชันบนวัตถุที่ผู้ร่วมประชุมตัดคำ
Grimm The Opiner

4
แทนที่จะดำเนินการฉันขอแนะนำอย่างยิ่งในกรณีของคุณเกี่ยวกับตัวดำเนินการโทร - void CCallback :: operator () (int) สาเหตุของเรื่องนี้คือในการเขียนโปรแกรมทั่วไปวัตถุที่เรียกได้คาดว่าจะเรียกว่าเป็นฟังก์ชั่น o.Execute (5) จะเข้ากันไม่ได้ แต่ o (5) จะพอดีกับอาร์กิวเมนต์เท็มเพลตที่เรียกได้ คลาสนี้อาจเป็นแบบทั่วไป แต่ฉันคิดว่าคุณทำให้มันง่ายสำหรับความกะทัดรัด (ซึ่งเป็นสิ่งที่ดี) นอกเหนือจาก nitpick นั่นเป็นคลาสที่มีประโยชน์มาก
David Peterson

18

ความจำเป็นในการใช้งาน C ++ เป็นสิ่งที่ทำให้อึดอัดใจในชุมชน C ++ เป็นเวลานาน โปรแกรมเมอร์ C ++ ทุกคนชอบที่จะมีดังนั้นในที่สุดพวกเขาก็ใช้มันแม้ว่าจะมีข้อเท็จจริงที่ว่า:

  1. std::function() ใช้การดำเนินการฮีป (และไม่สามารถเข้าถึงได้สำหรับการเขียนโปรแกรมแบบฝังอย่างจริงจัง)

  2. การใช้งานอื่น ๆ ทั้งหมดให้สัมปทานต่อการพกพาหรือความสอดคล้องมาตรฐานในระดับที่มากขึ้นหรือน้อยลง (โปรดตรวจสอบโดยการตรวจสอบการใช้งานของผู้ได้รับมอบหมายต่างๆที่นี่และใน codeproject) ฉันยังไม่เห็นการใช้งานที่ไม่ได้ใช้ wild reinterpret_casts, คลาสซ้อน "ต้นแบบ" ซึ่งหวังว่าจะผลิตตัวชี้ฟังก์ชั่นที่มีขนาดเดียวกับที่ผู้ใช้ส่งผ่าน, คอมไพเลอร์เล่ห์กลเช่นประกาศไปข้างหน้าก่อนแล้วพิมพ์ typedef แล้วประกาศอีกครั้ง คราวนี้สืบทอดมาจากคลาสอื่นหรือเทคนิคที่ร่มรื่นคล้ายกัน ในขณะที่มันเป็นความสำเร็จที่ยิ่งใหญ่สำหรับผู้ใช้งานที่สร้างสิ่งนั้น แต่ก็ยังคงเป็นประจักษ์พยานที่น่าเศร้าเกี่ยวกับการพัฒนาของ C ++

  3. มีเพียงน้อยนิดที่ชี้ให้เห็นว่าขณะนี้มีการปรับปรุงมาตรฐานมากกว่า 3 C ++ ตัวแทนไม่ได้รับการแก้ไขอย่างถูกต้อง (หรือขาดคุณสมบัติทางภาษาที่อนุญาตให้มีการใช้งานตัวแทนอย่างตรงไปตรงมา)

  4. ด้วยวิธีการกำหนดแลมบ์ดา C ++ 11 ตามมาตรฐาน (แต่ละแลมบ์ดามีการระบุชื่อประเภทต่าง ๆ ) สถานการณ์จะดีขึ้นในบางกรณีการใช้งาน แต่สำหรับกรณีการใช้งานการใช้ผู้รับมอบสิทธิ์ในไลบรารี API (DLL) แลมบ์ดาเพียงอย่างเดียวยังคงไม่สามารถใช้งานได้ เทคนิคทั่วไปที่นี่คือการแพ็คแลมบ์ดาครั้งแรกในฟังก์ชั่นมาตรฐาน :: แล้วส่งผ่านข้าม API


ฉันได้ทำการเรียกกลับทั่วไปของ Elbert Mai ใน C ++ 11 ตามแนวคิดอื่น ๆ ที่พบในที่อื่นและดูเหมือนจะชัดเจนว่าปัญหาส่วนใหญ่ที่คุณอ้างถึงใน 2) ปัญหาจู้จี้ที่เหลืออยู่เพียงอย่างเดียวสำหรับฉันคือฉันติดอยู่โดยใช้แมโครในรหัสลูกค้าเพื่อสร้างผู้ได้รับมอบหมาย อาจมีวิธีมายากลแม่แบบจากสิ่งนี้ซึ่งฉันยังไม่ได้แก้ไข
kert

3
ฉันใช้ std :: function โดยไม่มี heap ในระบบฝังตัวและยังคงใช้งานได้
ปราสาท

1
std::functionไม่ได้จัดสรรแบบไดนามิกเสมอ
Lightness Races ใน Orbit

3
คำตอบนี้เป็นคำ
Lightness Races ใน Orbit

8

อย่างง่ายมากผู้รับมอบสิทธิ์มอบหน้าที่การทำงานสำหรับวิธีที่ตัวชี้ฟังก์ชัน SHOULD ทำงาน มีข้อ จำกัด มากมายของพอยน์เตอร์ของฟังก์ชันใน C ++ ผู้รับมอบสิทธิ์ใช้สิ่งที่น่ารังเกียจเบื้องหลังเทมเพลตเพื่อสร้างเทมเพลตคลาสฟังก์ชั่นตัวชี้ประเภทที่ทำงานในแบบที่คุณอาจต้องการ

เช่น - คุณสามารถตั้งค่าให้ชี้ไปที่ฟังก์ชั่นที่กำหนดและคุณสามารถส่งผ่านพวกเขาไปรอบ ๆ และเรียกพวกเขาทุกที่ทุกเวลาที่คุณต้องการ

มีตัวอย่างที่ดีมากที่นี่:


4

ตัวเลือกสำหรับผู้รับมอบสิทธิ์ใน C ++ ที่ไม่ได้กล่าวถึงในที่นี้คือการใช้สไตล์ C โดยใช้ฟังก์ชัน ptr และอาร์กิวเมนต์บริบท นี่อาจเป็นรูปแบบเดียวกับที่หลายคนถามคำถามนี้เพื่อพยายามหลีกเลี่ยง แต่รูปแบบนั้นพกพาได้อย่างมีประสิทธิภาพและสามารถใช้งานได้ในสมองกลฝังตัวและรหัสเคอร์เนล

class SomeClass
{
    in someMember;
    int SomeFunc( int);

    static void EventFunc( void* this__, int a, int b, int c)
    {
        SomeClass* this_ = static_cast< SomeClass*>( this__);

        this_->SomeFunc( a );
        this_->someMember = b + c;
    }
};

void ScheduleEvent( void (*delegateFunc)( void*, int, int, int), void* delegateContext);

    ...
    SomeClass* someObject = new SomeObject();
    ...
    ScheduleEvent( SomeClass::EventFunc, someObject);
    ...

1

Windows Runtime เทียบเท่ากับวัตถุฟังก์ชันใน C ++ มาตรฐาน หนึ่งสามารถใช้ฟังก์ชั่นทั้งหมดเป็นพารามิเตอร์ (ที่จริงแล้วนั่นคือตัวชี้ฟังก์ชั่น) ส่วนใหญ่จะใช้ร่วมกับกิจกรรม ผู้รับมอบสิทธิ์แสดงถึงสัญญาที่ตัวจัดการเหตุการณ์ดำเนินการมาก ช่วยอำนวยความสะดวกในการทำงานของตัวชี้ฟังก์ชัน

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