C ++ फंक्शनलर्स और उनके उपयोग क्या हैं?


875

मैं C ++ में फंक्शनलर्स के बारे में बहुत कुछ सुनता रहता हूं। क्या कोई मुझे बता सकता है कि वे क्या हैं और वे किन मामलों में उपयोगी होंगे?


4
इस विषय के जवाब में इस विषय को शामिल किया गया है: stackoverflow.com/questions/317450/why-override-operator#317528
ल्यूक टॉरेल

2
इसका उपयोग C ++ में क्लोजर बनाने के लिए किया जाता है।
ताम्र। ०

नीचे दिए गए उत्तरों को देखते हुए, अगर कोई सोच रहा है कि operator()(...)इसका क्या मतलब है: यह "फ़ंक्शन कॉल" ऑपरेटर को ओवरलोड कर रहा है । यह ऑपरेटर के लिए बस ओवरलोडिंग है ()operator()कॉल किए गए फ़ंक्शन को कॉल करने में गलती न करें operator, लेकिन इसे सिंटैक्स को ओवरलोड करने वाले सामान्य ऑपरेटर के रूप में देखें।
zardosht

जवाबों:


1041

एक फ़नकार बहुत अधिक सिर्फ एक वर्ग है जो ऑपरेटर () को परिभाषित करता है। यह आपको एक फंक्शन बनाने वाली वस्तुएं बनाने देता है:

// this is a functor
struct add_x {
  add_x(int val) : x(val) {}  // Constructor
  int operator()(int y) const { return x + y; }

private:
  int x;
};

// Now you can use it like this:
add_x add42(42); // create an instance of the functor class
int i = add42(8); // and "call" it
assert(i == 50); // and it added 42 to its argument

std::vector<int> in; // assume this contains a bunch of values)
std::vector<int> out(in.size());
// Pass a functor to std::transform, which calls the functor on every element 
// in the input sequence, and stores the result to the output sequence
std::transform(in.begin(), in.end(), out.begin(), add_x(1)); 
assert(out[i] == in[i] + 1); // for all i

फंक्शनलर्स के बारे में कुछ अच्छी बातें हैं। एक यह है कि नियमित कार्यों के विपरीत, उनमें राज्य हो सकते हैं। उपरोक्त उदाहरण एक फ़ंक्शन बनाता है जो आप जो भी देते हैं उसमें 42 जोड़ता है। लेकिन वह मान ४२ हार्डकोडेड नहीं है, यह एक कंस्ट्रक्टर तर्क के रूप में निर्दिष्ट किया गया था जब हमने अपना फ़ंक्शनल इंस्टेंस बनाया था। मैं एक और योजक बना सकता हूं, जिसमें 27 जोड़ा गया, बस एक अलग मूल्य के साथ कंस्ट्रक्टर को कॉल करके। यह उन्हें अच्छी तरह से अनुकूलन बनाता है।

जैसा कि अंतिम पंक्तियाँ दिखाती हैं, आप अक्सर अन्य कार्यों जैसे कि std :: ट्रांस्फ़ॉर्म या अन्य मानक लाइब्रेरी एल्गोरिदम के तर्क के रूप में फ़ंक्शंस पास करते हैं। आप एक नियमित फंक्शन पॉइंटर को छोड़कर ऐसा कर सकते हैं, जैसा कि मैंने ऊपर कहा था, फंक्शनलर्स को "कस्टमाइज़्ड" किया जा सकता है क्योंकि उनमें स्टेट होते हैं, जिससे वे अधिक लचीले हो जाते हैं (यदि मैं फंक्शन पॉइंटर का उपयोग करना चाहता था, तो मुझे एक फंक्शन लिखना होगा। जो अपने तर्क में ठीक 1 जोड़ देता है। फ़नकार सामान्य है, और जो कुछ भी आपने इसे इसके साथ जोड़ा है) को जोड़ता है, और वे संभावित रूप से अधिक कुशल भी होते हैं। उपरोक्त उदाहरण में, कंपाइलर को पता है कि किस फ़ंक्शन std::transformको कॉल करना चाहिए। इसे बुलाना चाहिए add_x::operator()। इसका मतलब है कि यह फ़ंक्शन कॉल को इनलाइन कर सकता है। और यह इसे उतना ही कुशल बनाता है जैसे कि मैंने वेक्टर के प्रत्येक मान पर फ़ंक्शन को मैन्युअल रूप से कॉल किया था।

यदि मैंने इसके बजाय एक फ़ंक्शन पॉइंटर पास किया था, तो कंपाइलर तुरंत यह नहीं देख सकता है कि यह किस फ़ंक्शन को इंगित करता है, इसलिए जब तक यह कुछ काफी जटिल वैश्विक अनुकूलन नहीं करता है, तब तक इसे रनटाइम पर पॉइंटर को डीरेल करना होगा, और फिर कॉल करें।


32
क्या आप इस लाइन की व्याख्या कर सकते हैं, कृपया std :: transform (in.begin (), in.end (), out.begin (), add_x (1)); आप क्यों नहीं add_x लिखते हैं, add42 नहीं?
अलेक्स

102
@ दोनों ने काम किया होगा (लेकिन प्रभाव अलग होगा)। यदि मैंने उपयोग किया है add42, तो मैंने पहले बनाए गए फ़न्क्टर का उपयोग किया होगा, और प्रत्येक मूल्य में 42 जोड़े। साथ add_x(1)मैं functor, जिनमें केवल प्रत्येक मान से 1 कहते हैं का एक नया उदाहरण बना सकते हैं। यह केवल यह दिखाने के लिए है कि अक्सर, आप फ़नकार को "फ़्लाइ पर" तुरंत भेज देते हैं, जब आपको इसकी आवश्यकता होती है, तो इसे पहले बनाने के बजाय, और इसे आसपास रखने से पहले आप वास्तव में किसी भी चीज़ के लिए इसका उपयोग करते हैं।
jalf

8
@ ज़ादने बेशक। उन्हें बस होना चाहिए operator(), क्योंकि कॉल करने वाला इसका इस्तेमाल करने के लिए कहता है। क्या किसी और functor सदस्य काम करता है, निर्माताओं, ऑपरेटरों और सदस्य चर की है पूरी तरह से आप पर निर्भर है।
जलफ

4
@ rikimaru2013 कार्यात्मक प्रोग्रामिंग के प्रतिमान में, आप सही हैं, एक फ़ंक्शन भी एक फ़नकार है, लेकिन सी ++ के प्रतिरूप में, फ़ंक्टर विशेष रूप से एक फ़ंक्शन के रूप में उपयोग किया जाने वाला एक वर्ग है। पारिभाषिक शब्दावली का आरंभिक रूप से दुरुपयोग किया गया था, लेकिन विभाजन उपयोगी है और आज भी कायम है। यदि आप C ++ संदर्भ में "फ़ंक्शनलर्स" के रूप में फ़ंक्शन का उल्लेख करना शुरू करते हैं, तो आप सिर्फ बातचीत को भ्रमित करेंगे।
srm

6
क्या यह एक वर्ग है या कक्षा का एक उदाहरण है? ज्यादातर स्रोतों में, add42एक फ़नकार कहा जाता है, न कि add_x(जो फ़नकार का वर्ग है या फ़नकार वर्ग है)। मुझे लगता है कि शब्दावली सुसंगत है क्योंकि फंक्शंस को फ़ंक्शन ऑब्जेक्ट भी कहा जाता है , फ़ंक्शन क्लास नहीं। क्या आप इस बिंदु को स्पष्ट कर सकते हैं?
सर्गेई टैचेनोव

121

थोड़ा जोड़। आप boost::functionइस तरह के कार्यों और विधियों से फंक्शनलर्स बनाने के लिए उपयोग कर सकते हैं :

class Foo
{
public:
    void operator () (int i) { printf("Foo %d", i); }
};
void Bar(int i) { printf("Bar %d", i); }
Foo foo;
boost::function<void (int)> f(foo);//wrap functor
f(1);//prints "Foo 1"
boost::function<void (int)> b(&Bar);//wrap normal function
b(1);//prints "Bar 1"

और आप इस फ़नकारक में राज्य जोड़ने के लिए बढ़ावा :: बाइंड का उपयोग कर सकते हैं

boost::function<void ()> f1 = boost::bind(foo, 2);
f1();//no more argument, function argument stored in f1
//and this print "Foo 2" (:
//and normal function
boost::function<void ()> b1 = boost::bind(&Bar, 2);
b1();// print "Bar 2"

और सबसे उपयोगी, बढ़ावा देने के साथ :: बाँध और बढ़ावा देने :: :: आप क्लास विधि से फंक्शनल बना सकते हैं, वास्तव में यह एक प्रतिनिधि है:

class SomeClass
{
    std::string state_;
public:
    SomeClass(const char* s) : state_(s) {}

    void method( std::string param )
    {
        std::cout << state_ << param << std::endl;
    }
};
SomeClass *inst = new SomeClass("Hi, i am ");
boost::function< void (std::string) > callback;
callback = boost::bind(&SomeClass::method, inst, _1);//create delegate
//_1 is a placeholder it holds plase for parameter
callback("useless");//prints "Hi, i am useless"

आप फंक्शंस की सूची या वेक्टर बना सकते हैं

std::list< boost::function<void (EventArg e)> > events;
//add some events
....
//call them
std::for_each(
        events.begin(), events.end(), 
        boost::bind( boost::apply<void>(), _1, e));

यह सब सामान के साथ एक समस्या है, संकलक त्रुटि संदेश मानव पठनीय नहीं है :)


4
operator ()निजी से कक्षाएं डिफ़ॉल्ट होने के बाद आपके पहले उदाहरण में सार्वजनिक नहीं होना चाहिए ?
नाथनऑलवर

4
हो सकता है कि किसी समय यह उत्तर एक अद्यतन के योग्य हो, क्योंकि अब
लैंबडास

102

एक फ़नकार एक ऐसी वस्तु है जो एक फ़ंक्शन की तरह कार्य करती है। मूल रूप से, एक वर्ग जो परिभाषित करता है operator()

class MyFunctor
{
   public:
     int operator()(int x) { return x * 2;}
}

MyFunctor doubler;
int x = doubler(5);

वास्तविक लाभ यह है कि एक फ़नकार राज्य धारण कर सकता है।

class Matcher
{
   int target;
   public:
     Matcher(int m) : target(m) {}
     bool operator()(int x) { return x == target;}
}

Matcher Is5(5);

if (Is5(n))    // same as if (n == 5)
{ ....}

11
बस यह जोड़ने की जरूरत है कि उनका उपयोग फ़ंक्शन पॉइंटर की तरह किया जा सकता है।
मार्टिन यॉर्क

7
@ लोकीअस्तारी - उन लोगों के लिए जो अवधारणा के लिए नए हैं, यह थोड़ा भ्रामक हो सकता है। फंक्शन डॉक्टर्स को "फंक्शन पॉइंटर्स" की जगह "हमेशा की तरह" इस्तेमाल किया जा सकता है, लेकिन हमेशा नहीं। उदाहरण के लिए, एक फ़ंक्शन जो फ़ंक्शन पॉइंटर लेता है, वह फ़ंक्टर को उसके स्थान पर नहीं ले जा सकता है, भले ही फ़नकार के पास फ़ंक्शन तर्कों के समान तर्क और वापसी मूल्य हो। लेकिन डिजाइन करते समय बड़े और पसंदीदा, सैद्धांतिक रूप से जाने के लिए पसंदीदा और सैद्धांतिक रूप से "अधिक आधुनिक" तरीका है।
मेसनविंसर

intजब वह वापस लौटता है तो दूसरा क्यों लौटता है bool? यह C ++ है, C. नहीं। जब यह उत्तर लिखा गया था, boolतब मौजूद नहीं था?
निधि मोनिका का मुकदमा

@QPaysTaxes एक टाइपो मुझे लगता है। मैं शायद पहले उदाहरण से कोड को कॉपी करता हूं और इसे बदलना भूल गया। मैंने अभी इसे ठीक किया है।
जेम्स क्यूरन

1
@ रियासत अगर मैच्योर लाइब्रेरी में है, तो Is5 को परिभाषित करना () काफी सरल है। AND से आप Is7 (), Is32 () आदि बना सकते हैं, यह एक उदाहरण है। वे functor अधिक जटिल हो सकता है।
जेम्स क्यूरन

51

नाम "फ़ंक्टर" पारंपरिक रूप से श्रेणी सिद्धांत में उपयोग किया गया है, जब तक कि सी + + दृश्य पर दिखाई नहीं दिया। इसका फ़नकार के C ++ कॉन्सेप्ट से कोई लेना-देना नहीं है। C ++ में जिसे हम "functor" कहते हैं, उसके बजाय नाम फ़ंक्शन ऑब्जेक्ट का उपयोग करना बेहतर है । इसी तरह से अन्य प्रोग्रामिंग लैंग्वेज भी इसी तरह के निर्माणों को बुलाती हैं।

सादे समारोह के बजाय इस्तेमाल किया:

विशेषताएं:

  • फंक्शन ऑब्जेक्ट में स्थिति हो सकती है
  • फंक्शन ऑब्जेक्ट OOP में फिट होता है (यह हर दूसरी वस्तु की तरह व्यवहार करता है)।

विपक्ष:

  • कार्यक्रम में अधिक जटिलता लाता है।

फ़ंक्शन पॉइंटर के बजाय उपयोग किया जाता है:

विशेषताएं:

  • कार्य वस्तु अक्सर झुकी हो सकती है

विपक्ष:

  • फंक्शन ऑब्जेक्ट को रनटाइम के दौरान अन्य फ़ंक्शन ऑब्जेक्ट प्रकार के साथ स्वैप नहीं किया जा सकता है (कम से कम जब तक यह कुछ आधार वर्ग का विस्तार नहीं करता है, इसलिए यह ओवरहेड देता है)

वर्चुअल फ़ंक्शन के बजाय उपयोग किया जाता है:

विशेषताएं:

  • फंक्शन ऑब्जेक्ट (नॉन-वर्चुअल) को वाइबल और रनटाइम डिस्पैचिंग की आवश्यकता नहीं होती है, इस प्रकार यह ज्यादातर मामलों में अधिक कुशल होता है

विपक्ष:

  • फंक्शन ऑब्जेक्ट को रनटाइम के दौरान अन्य फ़ंक्शन ऑब्जेक्ट प्रकार के साथ स्वैप नहीं किया जा सकता है (कम से कम जब तक यह कुछ आधार वर्ग का विस्तार नहीं करता है, इसलिए यह ओवरहेड देता है)

1
क्या आप इन उदाहरणों को वास्तविक उदाहरण में समझा सकते हैं? हम पॉलीमॉर्फिज़्म एडन फ़नक्शन पॉइंटर के रूप में फ़ंक्शनलर्स का उपयोग कैसे कर सकते हैं?
मिलाद खाजवी

1
वास्तव में क्या मतलब है कि एक फ़नकार राज्य रखता है?
इरोगोल

यह इंगित करने के लिए धन्यवाद कि किसी को किसी प्रकार के बहुरूपता के लिए आधार वर्ग की आवश्यकता है। मुझे बस समस्या यह है कि मुझे एक फंक्शनर को एक साधारण फ़ंक्शन पॉइंटर के रूप में एक ही स्थान पर उपयोग करना है और मुझे जो एकमात्र तरीका मिला है वह था एक फ़नकार बेस क्लास लिखना (जैसा कि मैं C ++ 11 सामान का उपयोग नहीं कर सकता)। अगर यह ओवरहेड आपके जवाब को पढ़ने तक समझ में नहीं आता है, तो यकीन नहीं होता।
आईडीक्लेव ४६३०३५18१

1
@ ईरोगोल एक फंटर एक वस्तु है जो सिंटैक्स का समर्थन करने के लिए होता है foo(arguments)। इसलिए, इसमें चर हो सकते हैं; उदाहरण के लिए, यदि आपके पास कोई update_password(string)फ़ंक्शन था, तो आप इस बात का ट्रैक रखना चाह सकते हैं कि कितनी बार हुआ था; एक अंतिम संस्कार के साथ, private long timeयह अंतिम बार होने वाले टाइमस्टैम्प का प्रतिनिधित्व कर सकता है । फ़ंक्शन पॉइंटर या सादे फ़ंक्शन के साथ, आपको इसके नामस्थान के बाहर एक वैरिएबल का उपयोग करने की आवश्यकता होगी, जो केवल परिभाषा और परिभाषा के बजाय दस्तावेज़ीकरण और उपयोग से संबंधित है।
फंड

4
That यह उल्लेख करने के लिए कि नाम बिना किसी कारण के बनाया गया है। मैं बस खोज रहा हूँ कि गणितीय (या यदि आप चाहते हैं तो कार्यात्मक) के बीच संबंध क्या है, तो फ़ंक्टर और सी ++ से।
हाय-एंजेल

41

जैसा कि दूसरों ने उल्लेख किया है, एक फ़नकार एक ऐसी वस्तु है जो एक फ़ंक्शन की तरह काम करती है, अर्थात यह फ़ंक्शन कॉल ऑपरेटर को अधिभारित करती है।

आमतौर पर एसटीएल एल्गोरिदम में फंडर्स का उपयोग किया जाता है। वे उपयोगी हैं क्योंकि वे फ़ंक्शन कॉल के पहले और बीच में राज्य पकड़ सकते हैं, कार्यात्मक भाषाओं में एक बंद की तरह। उदाहरण के लिए, आप एक MultiplyByफ़नकार को परिभाषित कर सकते हैं जो एक निर्दिष्ट राशि से इसके तर्क को गुणा करता है:

class MultiplyBy {
private:
    int factor;

public:
    MultiplyBy(int x) : factor(x) {
    }

    int operator () (int other) const {
        return factor * other;
    }
};

तब आप किसी MultiplyByवस्तु को एल्गोरिथ्म जैसे std :: ट्रांस्फ़ॉर्म पास कर सकते हैं :

int array[5] = {1, 2, 3, 4, 5};
std::transform(array, array + 5, array, MultiplyBy(3));
// Now, array is {3, 6, 9, 12, 15}

एक फ़ंक्शन के लिए एक पॉइंटर पर एक फ़नकार का एक और लाभ यह है कि कॉल को अधिक मामलों में इनलाइन किया जा सकता है। यदि आपने एक फ़ंक्शन पॉइंटर को पास किया है transform, जब तक कि कॉल इनलाइन नहीं हो जाती है और कंपाइलर जानता है कि आप हमेशा उसी फ़ंक्शन को पास करते हैं, तो यह पॉइंटर के माध्यम से कॉल को इनलाइन नहीं कर सकता है।


37

हमारे बीच मेरे जैसे नए लोगों के लिए: एक छोटे से शोध के बाद मुझे पता चला कि कोड जैल ने क्या किया।

एक फ़नकार एक वर्ग या संरचना वस्तु है जिसे एक फ़ंक्शन की तरह "कॉल" किया जा सकता है। यह ओवरलोडिंग द्वारा संभव बनाया गया है () operator() operator(यकीन नहीं क्या इसके कहा जाता है) तर्क के किसी भी संख्या ले जा सकते हैं। अन्य ऑपरेटर केवल दो लेते हैं यानी + operatorकेवल दो मान ले सकते हैं (ऑपरेटर के प्रत्येक पक्ष पर) और इसके लिए आपने जो भी मूल्य लिया है उसे वापस कर सकते हैं। आप किसी भी संख्या में तर्कों को फिट कर सकते हैं () operator, जो कि इसे इसका लचीलापन देता है।

एक फ़नकार बनाने के लिए सबसे पहले आप अपनी कक्षा बनाएँ। फिर आप अपनी पसंद के प्रकार और नाम के पैरामीटर के साथ कक्षा के लिए एक कंस्ट्रक्टर बनाते हैं। इसके बाद एक प्रारंभिक सूची (उसी कॉलोन ऑपरेटर का उपयोग करता है, जो कि मैं भी नया था) द्वारा एक ही स्टेटमेंट का अनुसरण किया जाता है, जो क्लास मेंबर्स ऑब्जेक्ट्स को कंस्ट्रक्टर को पहले घोषित पैरामीटर के साथ बनाता है। फिर () operatorओवरलोड हो जाता है। अंत में आप अपने द्वारा बनाई गई कक्षा या संरचना की निजी वस्तुओं की घोषणा करते हैं।

मेरा कोड (मुझे jalf का चर नाम भ्रमित करने वाला लगा)

class myFunctor
{ 
    public:
        /* myFunctor is the constructor. parameterVar is the parameter passed to
           the constructor. : is the initializer list operator. myObject is the
           private member object of the myFunctor class. parameterVar is passed
           to the () operator which takes it and adds it to myObject in the
           overloaded () operator function. */
        myFunctor (int parameterVar) : myObject( parameterVar ) {}

        /* the "operator" word is a keyword which indicates this function is an 
           overloaded operator function. The () following this just tells the
           compiler that () is the operator being overloaded. Following that is
           the parameter for the overloaded operator. This parameter is actually
           the argument "parameterVar" passed by the constructor we just wrote.
           The last part of this statement is the overloaded operators body
           which adds the parameter passed to the member object. */
        int operator() (int myArgument) { return myObject + myArgument; }

    private: 
        int myObject; //Our private member object.
}; 

अगर इस में से कोई भी गलत या सिर्फ सादा गलत है तो मुझे ठीक करने के लिए स्वतंत्र महसूस करें!


1
() ऑपरेटर को फ़ंक्शन-कॉल ऑपरेटर कहा जाता है। मुझे लगता है कि आप इसे कोष्ठक संचालक भी कह सकते हैं।
गौतम

4
"यह पैरामीटर वास्तव में तर्क है" पैरामीटर विवर "जिसे हमने अभी-अभी लिखा है" कंस्ट्रक्टर द्वारा पारित किया गया है ?
ऑर्बिट

22

एक फंक्टर एक उच्च-क्रम फ़ंक्शन है जो पैरामीरिज्ड (यानी टेम्पर्ड) प्रकारों के लिए एक फ़ंक्शन लागू करता है। यह मानचित्र उच्च-क्रम फ़ंक्शन का सामान्यीकरण है । उदाहरण के लिए, हम std::vectorइस तरह से एक फ़नकार को परिभाषित कर सकते हैं :

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::vector<U> fmap(F f, const std::vector<T>& vec)
{
    std::vector<U> result;
    std::transform(vec.begin(), vec.end(), std::back_inserter(result), f);
    return result;
}

यह फंक्शन लेता है std::vector<T>और std::vector<U>जब कोई फंक्शन देता है तो रिटर्न देता Fहै Tऔर रिटर्न देता है U। एक फ़नकार को कंटेनर प्रकारों पर परिभाषित नहीं करना पड़ता है, इसे किसी भी टेम्पर्ड प्रकार के लिए भी परिभाषित किया जा सकता है, जिसमें शामिल हैं std::shared_ptr:

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::shared_ptr<U> fmap(F f, const std::shared_ptr<T>& p)
{
    if (p == nullptr) return nullptr;
    else return std::shared_ptr<U>(new U(f(*p)));
}

एक सरल उदाहरण है जो टाइप को एक में परिवर्तित करता है double:

double to_double(int x)
{
    return x;
}

std::shared_ptr<int> i(new int(3));
std::shared_ptr<double> d = fmap(to_double, i);

std::vector<int> is = { 1, 2, 3 };
std::vector<double> ds = fmap(to_double, is);

दो कानून हैं जिन्हें फॉलोवर्स को पालन करना चाहिए। पहला पहचान कानून है, जिसमें कहा गया है कि यदि अंतिम संस्कारकर्ता को पहचान समारोह दिया जाता है, तो यह पहचान समारोह को प्रकार पर लागू करने fmap(identity, x)के समान होना चाहिए , जो कि समान होना चाहिए identity(x):

struct identity_f
{
    template<class T>
    T operator()(T x) const
    {
        return x;
    }
};
identity_f identity = {};

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<int> is1 = fmap(identity, is);
std::vector<int> is2 = identity(is);

अगला कानून कंपोजीशन कानून है, जिसमें कहा गया है कि यदि फ़ंक्टर को दो फ़ंक्शंस की संरचना दी जाती है, तो यह पहले फ़ंक्शन के लिए फ़ंक्टर को लागू करने और फिर दूसरे फ़ंक्शन के लिए समान होना चाहिए। तो, fmap(std::bind(f, std::bind(g, _1)), x)जैसा होना चाहिए fmap(f, fmap(g, x)):

double to_double(int x)
{
    return x;
}

struct foo
{
    double x;
};

foo to_foo(double x)
{
    foo r;
    r.x = x;
    return r;
}

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<foo> is1 = fmap(std::bind(to_foo, std::bind(to_double, _1)), is);
std::vector<foo> is2 = fmap(to_foo, fmap(to_double, is));

2
अनुच्छेद कि functor उनका तर्क है सही ढंग से इस अर्थ के लिए इस्तेमाल किया जाना चाहिए (यह भी देखें en.wikipedia.org/wiki/Functor ), और कहा कि समारोह वस्तुओं के लिए उपयोग सिर्फ लापरवाही है: jackieokay.com/2017/01/26/functors.html यह इसके लिए बहुत देर हो सकती है, हालांकि, यहां उन जवाबों की संख्या दी गई है जो केवल फ़ंक्शन ऑब्जेक्ट अर्थ पर विचार करते हैं।
धनुष

2
यह उत्तर> 700 अपवोट्स वाला होना चाहिए। जैसा कि कोई व्यक्ति हास्केल को C ++ से बेहतर जानता है, सी ++ लिंगुआ ने मुझे हर समय हैरान कर दिया।
mschmidt

श्रेणी सिद्धांत और सी ++? क्या यह बार्टोज़ मिल्वेस्की का गुप्त एसओ खाता है?
मतीन उल्हाक

1
फनकार कानूनों को मानक संकेतन में संक्षेपित करने में सहायक हो सकता है: fmap(id, x) = id(x)और fmap(f ◦ g, x) = fmap(f, fmap(g, x))
मतीन उल्हाक

@mschmidt whilst के फन्नेकार का भी यही अर्थ है, C ++ ने "फ़ंक्शन ऑब्जेक्ट" के रूप में एक ही मतलब
निकालने के

9

यहां एक वास्तविक स्थिति है जहां मुझे अपनी समस्या को हल करने के लिए एक फ़नकार का उपयोग करने के लिए मजबूर किया गया था:

मेरे पास कार्यों का एक सेट है (कहते हैं, उनमें से 20), और वे सभी समान हैं, प्रत्येक कॉल को 3 विशिष्ट स्पॉट में एक अलग विशिष्ट फ़ंक्शन को छोड़कर।

यह अविश्वसनीय बर्बादी है, और कोड दोहराव है। आम तौर पर मैं सिर्फ एक फ़ंक्शन पॉइंटर में पास होता हूं, और बस 3 स्थानों में कॉल करता हूं। (इसलिए कोड को केवल बीस बार के बजाय एक बार प्रदर्शित होने की आवश्यकता है।)

लेकिन फिर मुझे एहसास हुआ, प्रत्येक मामले में, विशिष्ट फ़ंक्शन को पूरी तरह से अलग पैरामीटर प्रोफाइल की आवश्यकता थी! कभी 2 पैरामीटर, कभी 5 पैरामीटर आदि।

एक अन्य समाधान एक आधार वर्ग होगा, जहां विशिष्ट कार्य एक व्युत्पन्न वर्ग में एक ओवरराइड विधि है। लेकिन क्या मैं वास्तव में इस सभी का निर्माण करना चाहता हूं, बस इसलिए मैं एक फ़ंक्शन पॉइंटर पास कर सकता हूं ????

समाधान: तो मैंने जो किया था, मैंने एक रैपर क्लास (एक "फ़नकार") बनाया, जो मेरे द्वारा कहे गए किसी भी कार्य को कॉल करने में सक्षम है। मैंने इसे पहले से निर्धारित किया है (इसके मापदंडों, आदि के साथ) और फिर मैं इसे फ़ंक्शन पॉइंटर के बजाय पास करता हूं। अब कहा जाने वाला कोड फनकार को ट्रिगर कर सकता है, बिना यह जाने कि अंदर क्या हो रहा है। यह इसे कई बार कॉल कर सकता है (मुझे 3 बार कॉल करने की आवश्यकता है।)


यही है - एक व्यावहारिक उदाहरण जहां एक फ़नकार स्पष्ट और आसान समाधान निकला, जिसने मुझे 20 कार्यों से 1 तक कोड दोहराव को कम करने की अनुमति दी।


3
यदि आपके फ़ंक्टर को अलग-अलग विशिष्ट फ़ंक्शन कहा जाता है, और ये अन्य फ़ंक्शंस उन मापदंडों की संख्या में भिन्न होते हैं, जिन्हें वे स्वीकार करते हैं, तो क्या इसका मतलब है कि आपके फ़ंक्टर ने इन अन्य फ़ंक्शंस को भेजने के लिए चर संख्या में तर्क स्वीकार किए हैं?
जॉन्कर्स

4
क्या आप कृपया कोड के कुछ भाग को उद्धृत करके उपरोक्त परिदृश्य को स्पष्ट कर सकते हैं, मैं c ++ में नया हूं इस अवधारणा को समझना चाहता हूं ..
sanjeev

3

के लिए कॉलबैक में इस्तेमाल के अलावा, सी ++ functors भी मदद एक प्रदान करने के लिए कर सकते हैं मैटलैब एक को पसंद करने पहुँच शैली मैट्रिक्स वर्ग। एक उदाहरण है


यह (मैट्रिक्स उदाहरण) सादे उपयोग है, operator()लेकिन फ़ंक्शन ऑब्जेक्ट गुणों का उपयोग नहीं कर रहा है।
9

3

जैसे दोहराया गया है, फंक्शनलर्स ऐसी कक्षाएं हैं जिन्हें फ़ंक्शंस (ओवरलोड ऑपरेटर ()) के रूप में माना जा सकता है।

वे उन स्थितियों के लिए सबसे अधिक उपयोगी होते हैं जिनमें आपको किसी फ़ंक्शन को बार-बार या विलंबित कॉल के साथ कुछ डेटा को संबद्ध करने की आवश्यकता होती है।

उदाहरण के लिए, फंक्शनलर्स की एक लिंक्ड-लिस्ट का इस्तेमाल एक बेसिक लो-ओवरहेड सिंक्रोनस कोरूटीन सिस्टम, एक टास्क डिस्पैचर या इंटरसेबल फाइल पार्सिंग को लागू करने के लिए किया जा सकता है। उदाहरण:

/* prints "this is a very simple and poorly used task queue" */
class Functor
{
public:
    std::string output;
    Functor(const std::string& out): output(out){}
    operator()() const
    {
        std::cout << output << " ";
    }
};

int main(int argc, char **argv)
{
    std::list<Functor> taskQueue;
    taskQueue.push_back(Functor("this"));
    taskQueue.push_back(Functor("is a"));
    taskQueue.push_back(Functor("very simple"));
    taskQueue.push_back(Functor("and poorly used"));
    taskQueue.push_back(Functor("task queue"));
    for(std::list<Functor>::iterator it = taskQueue.begin();
        it != taskQueue.end(); ++it)
    {
        *it();
    }
    return 0;
}

/* prints the value stored in "i", then asks you if you want to increment it */
int i;
bool should_increment;
int doSomeWork()
{
    std::cout << "i = " << i << std::endl;
    std::cout << "increment? (enter the number 1 to increment, 0 otherwise" << std::endl;
    std::cin >> should_increment;
    return 2;
}
void doSensitiveWork()
{
     ++i;
     should_increment = false;
}
class BaseCoroutine
{
public:
    BaseCoroutine(int stat): status(stat), waiting(false){}
    void operator()(){ status = perform(); }
    int getStatus() const { return status; }
protected:
    int status;
    bool waiting;
    virtual int perform() = 0;
    bool await_status(BaseCoroutine& other, int stat, int change)
    {
        if(!waiting)
        {
            waiting = true;
        }
        if(other.getStatus() == stat)
        {
            status = change;
            waiting = false;
        }
        return !waiting;
    }
}

class MyCoroutine1: public BaseCoroutine
{
public:
    MyCoroutine1(BaseCoroutine& other): BaseCoroutine(1), partner(other){}
protected:
    BaseCoroutine& partner;
    virtual int perform()
    {
        if(getStatus() == 1)
            return doSomeWork();
        if(getStatus() == 2)
        {
            if(await_status(partner, 1))
                return 1;
            else if(i == 100)
                return 0;
            else
                return 2;
        }
    }
};

class MyCoroutine2: public BaseCoroutine
{
public:
    MyCoroutine2(bool& work_signal): BaseCoroutine(1), ready(work_signal) {}
protected:
    bool& work_signal;
    virtual int perform()
    {
        if(i == 100)
            return 0;
        if(work_signal)
        {
            doSensitiveWork();
            return 2;
        }
        return 1;
    }
};

int main()
{
     std::list<BaseCoroutine* > coroutineList;
     MyCoroutine2 *incrementer = new MyCoroutine2(should_increment);
     MyCoroutine1 *printer = new MyCoroutine1(incrementer);

     while(coroutineList.size())
     {
         for(std::list<BaseCoroutine *>::iterator it = coroutineList.begin();
             it != coroutineList.end(); ++it)
         {
             *it();
             if(*it.getStatus() == 0)
             {
                 coroutineList.erase(it);
             }
         }
     }
     delete printer;
     delete incrementer;
     return 0;
}

बेशक, ये उदाहरण अपने आप में उपयोगी नहीं हैं। वे केवल यह दिखाते हैं कि कैसे फंक्शंस उपयोगी हो सकते हैं, फंक्शंस स्वयं बहुत ही बुनियादी और अनम्य हैं और यह उन्हें कम उपयोगी बनाता है, उदाहरण के लिए, क्या बढ़ावा प्रदान करता है।


2

कुछ GUI बटन को वास्तविक C ++ फंक्शन या मेथड से कनेक्ट करने के लिए gtkmm में Functors का इस्तेमाल किया जाता है।


यदि आप अपने ऐप को मल्टीथ्रेडेड बनाने के लिए pthread लाइब्रेरी का उपयोग करते हैं, तो Functors आपकी मदद कर सकते हैं।
एक सूत्र को शुरू करने के लिए, pthread_create(..)अपने स्वयं के थ्रेड पर निष्पादित किए जाने वाले फ़ंक्शन पॉइंटर का एक तर्क है ।
लेकिन एक असुविधा है। यह पॉइंटर एक विधि का सूचक नहीं हो सकता है, जब तक कि यह एक स्थिर विधि नहीं है , या जब तक आप यह निर्दिष्ट नहीं करते हैं कि यह वर्ग है , जैसे class::method। और एक और बात, आपके तरीके का इंटरफ़ेस केवल यह हो सकता है:

void* method(void* something)

तो आप (एक साधारण स्पष्ट तरीके से) नहीं चला सकते हैं, कुछ अतिरिक्त किए बिना एक धागे में अपनी कक्षा से विधियां।

C ++ में थ्रेड्स से निपटने का एक बहुत अच्छा तरीका, आपकी खुद की Threadक्लास बना रहा है। यदि आप MyClassकक्षा से विधियों को चलाना चाहते हैं , तो मैंने जो किया था, उन तरीकों को Functorव्युत्पन्न कक्षाओं में बदल दें ।

इसके अलावा, Threadकक्षा में यह विधि है: इस पद्धति के लिए static void* startThread(void* arg)
एक सूचक को कॉल करने के लिए एक तर्क के रूप में उपयोग किया जाएगा pthread_create(..)। और startThread(..)आर्ग में जो प्राप्त करना चाहिए वह void*किसी भी Functorव्युत्पन्न वर्ग के ढेर में एक उदाहरण के लिए एक संदर्भित संदर्भ है , जिसे Functor*निष्पादित होने पर वापस डाला जाएगा, और फिर इसे run()विधि कहा जाएगा ।


2

जोड़ने के लिए, मैंने मौजूदा ऑब्जेक्ट को कमांड पैटर्न में फिट करने के लिए फ़ंक्शन ऑब्जेक्ट का उपयोग किया है; (केवल वही स्थान जहाँ OO प्रतिमान की सुंदरता सही OCP मुझे लगा); साथ ही यहाँ संबंधित फंक्शन अडैप्टर पैटर्न को जोड़ना।

मान लीजिए कि आपके तरीके पर हस्ताक्षर हैं:

int CTask::ThreeParameterTask(int par1, int par2, int par3)

हम देखेंगे कि हम इसे कमांड पैटर्न के लिए कैसे फिट कर सकते हैं - इसके लिए, पहले, आपको एक सदस्य फ़ंक्शन एडेप्टर लिखना होगा ताकि इसे फ़ंक्शन ऑब्जेक्ट के रूप में कहा जा सके।

नोट - यह बदसूरत है, और आप बूस्ट बाइंड हेल्पर्स आदि का उपयोग कर सकते हैं, लेकिन यदि आप नहीं कर सकते हैं या नहीं करना चाहते हैं, तो यह एक तरीका है।

// a template class for converting a member function of the type int        function(int,int,int)
//to be called as a function object
template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
class mem_fun3_t
{
  public:
explicit mem_fun3_t(_Ret (_Class::*_Pm)(_arg1,_arg2,_arg3))
    :m_Ptr(_Pm) //okay here we store the member function pointer for later use
    {}

//this operator call comes from the bind method
_Ret operator()(_Class *_P, _arg1 arg1, _arg2 arg2, _arg3 arg3) const
{
    return ((_P->*m_Ptr)(arg1,arg2,arg3));
}
private:
_Ret (_Class::*m_Ptr)(_arg1,_arg2,_arg3);// method pointer signature
};

इसके अलावा, हमें कॉल करने में सहायता के लिए उपरोक्त वर्ग के लिए एक सहायक विधि mem_fun3 की आवश्यकता है।

template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3> mem_fun3 ( _Ret (_Class::*_Pm)          (_arg1,_arg2,_arg3) )
{
  return (mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3>(_Pm));

}

अब, मापदंडों को बांधने के लिए, हमें एक बाइंडर फ़ंक्शन लिखना होगा। तो, यहाँ यह जाता है:

template<typename _Func,typename _Ptr,typename _arg1,typename _arg2,typename _arg3>
class binder3
{
public:
//This is the constructor that does the binding part
binder3(_Func fn,_Ptr ptr,_arg1 i,_arg2 j,_arg3 k)
    :m_ptr(ptr),m_fn(fn),m1(i),m2(j),m3(k){}

 //and this is the function object 
 void operator()() const
 {
        m_fn(m_ptr,m1,m2,m3);//that calls the operator
    }
private:
    _Ptr m_ptr;
    _Func m_fn;
    _arg1 m1; _arg2 m2; _arg3 m3;
};

और, binder3 वर्ग का उपयोग करने के लिए एक सहायक कार्य - bind3:

//a helper function to call binder3
template <typename _Func, typename _P1,typename _arg1,typename _arg2,typename _arg3>
binder3<_Func, _P1, _arg1, _arg2, _arg3> bind3(_Func func, _P1 p1,_arg1 i,_arg2 j,_arg3 k)
{
    return binder3<_Func, _P1, _arg1, _arg2, _arg3> (func, p1,i,j,k);
}

अब, हमें कमांड क्लास के साथ इसका उपयोग करना होगा; निम्न टाइपफ़ीड का उपयोग करें:

typedef binder3<mem_fun3_t<int,T,int,int,int> ,T* ,int,int,int> F3;
//and change the signature of the ctor
//just to illustrate the usage with a method signature taking more than one parameter
explicit Command(T* pObj,F3* p_method,long timeout,const char* key,
long priority = PRIO_NORMAL ):
m_objptr(pObj),m_timeout(timeout),m_key(key),m_value(priority),method1(0),method0(0),
method(0)
{
    method3 = p_method;
}

यहाँ आप इसे कैसे कहते हैं:

F3 f3 = PluginThreadPool::bind3( PluginThreadPool::mem_fun3( 
      &CTask::ThreeParameterTask), task1,2122,23 );

नोट: f3 (); टास्क 1 को कॉल करेगा-> थ्रीपैरमीटरटैस्क (21,22,23) ;।

निम्नलिखित लिंक पर इस पैटर्न का पूरा संदर्भ


2

फ़ंक्शंस को लागू करने का एक बड़ा लाभ यह है कि वे कॉल के बीच स्थिति को बनाए रख सकते हैं और पुन: उपयोग कर सकते हैं। उदाहरण के लिए, कई डायनेमिक प्रोग्रामिंग एल्गोरिदम, जैसे वैगनर-फिशर एल्गोरिथ्म , स्ट्रिंग्स के बीच लेवेन्शेटिन दूरी की गणना के लिए , परिणामों की एक बड़ी तालिका में भरकर काम करते हैं। इस तालिका को हर बार फ़ंक्शन कहा जाता है, इसलिए इसे आवंटित करना बहुत ही अक्षम है, इसलिए फ़ंक्शन को फ़नकार के रूप में लागू करना और तालिका को सदस्य चर बनाने से प्रदर्शन में सुधार हो सकता है।

नीचे एक फ़नर के रूप में वैगनर-फिशर एल्गोरिदम को लागू करने का एक उदाहरण है। ध्यान दें कि तालिका को कैसे कंस्ट्रक्टर में आवंटित किया गया है, और फिर आवश्यकतानुसार पुन: उपयोग के operator()साथ पुन: उपयोग किया जाता है।

#include <string>
#include <vector>
#include <algorithm>

template <typename T>
T min3(const T& a, const T& b, const T& c)
{
   return std::min(std::min(a, b), c);
}

class levenshtein_distance 
{
    mutable std::vector<std::vector<unsigned int> > matrix_;

public:
    explicit levenshtein_distance(size_t initial_size = 8)
        : matrix_(initial_size, std::vector<unsigned int>(initial_size))
    {
    }

    unsigned int operator()(const std::string& s, const std::string& t) const
    {
        const size_t m = s.size();
        const size_t n = t.size();
        // The distance between a string and the empty string is the string's length
        if (m == 0) {
            return n;
        }
        if (n == 0) {
            return m;
        }
        // Size the matrix as necessary
        if (matrix_.size() < m + 1) {
            matrix_.resize(m + 1, matrix_[0]);
        }
        if (matrix_[0].size() < n + 1) {
            for (auto& mat : matrix_) {
                mat.resize(n + 1);
            }
        }
        // The top row and left column are prefixes that can be reached by
        // insertions and deletions alone
        unsigned int i, j;
        for (i = 1;  i <= m; ++i) {
            matrix_[i][0] = i;
        }
        for (j = 1; j <= n; ++j) {
            matrix_[0][j] = j;
        }
        // Fill in the rest of the matrix
        for (j = 1; j <= n; ++j) {
            for (i = 1; i <= m; ++i) {
                unsigned int substitution_cost = s[i - 1] == t[j - 1] ? 0 : 1;
                matrix_[i][j] =
                    min3(matrix_[i - 1][j] + 1,                 // Deletion
                    matrix_[i][j - 1] + 1,                      // Insertion
                    matrix_[i - 1][j - 1] + substitution_cost); // Substitution
            }
        }
        return matrix_[m][n];
    }
};

1

किसी फ़ंक्शन में किसी स्थानीय फ़ंक्शन को परिभाषित करने के लिए फ़ंक्टर का भी उपयोग किया जा सकता है। प्रश्न और दूसरे का संदर्भ लें ।

लेकिन एक स्थानीय फ़नकार ऑटो चर के बाहर नहीं पहुंच सकता है। लैम्ब्डा (C ++ 11) फ़ंक्शन एक बेहतर समाधान है।


-10

मैंने फंक्शनलर्स का एक बहुत ही दिलचस्प उपयोग "खोज" किया है: मैं उनका उपयोग तब करता हूं जब मेरे पास एक विधि के लिए एक अच्छा नाम नहीं है, क्योंकि एक फंक्टर बिना नाम का एक तरीका है; ;-)


आप किसी फ़नकार को "बिना नाम वाली पद्धति" क्यों कहते हैं?
एंडरसन ग्रीन

5
बिना नाम के एक कार्य को लंबोदर कहा जाता है।
पॉल फुल्ट्ज II
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.