Apa itu fungsi C ++ dan kegunaannya?

876

Saya terus mendengar banyak tentang functors di C ++. Dapatkah seseorang memberi saya gambaran umum tentang apa itu mereka dan dalam kasus apa mereka akan berguna?

Konrad
sumber
4
Subjek ini telah dibahas sebagai jawaban atas pertanyaan ini: stackoverflow.com/questions/317450/why-override-operator #
317528
2
Ini digunakan untuk membuat penutupan di C ++.
copper.hat
Melihat jawaban di bawah, jika seseorang bertanya-tanya apa operator()(...)artinya: itu berlebihan operator "panggilan fungsi" . Ini hanya kelebihan operator untuk ()operator. Jangan salah operator()dengan memanggil fungsi yang dipanggil operator, tetapi melihatnya sebagai sintaks berlebihan operator yang biasa.
zardosht

Jawaban:

1041

Functor cukup banyak hanya kelas yang mendefinisikan operator (). Itu memungkinkan Anda membuat objek yang "mirip" fungsi:

// 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

Ada beberapa hal baik tentang functors. Salah satunya adalah bahwa tidak seperti fungsi biasa, mereka dapat berisi keadaan. Contoh di atas membuat fungsi yang menambahkan 42 ke apa pun yang Anda berikan. Tetapi nilai 42 itu bukan hardcoded, itu ditentukan sebagai argumen konstruktor ketika kami membuat instance functor kami. Saya dapat membuat penambah lain, yang menambahkan 27, hanya dengan memanggil konstruktor dengan nilai yang berbeda. Ini membuat mereka dapat dikustomisasi dengan baik.

Seperti yang ditampilkan baris terakhir, Anda sering memberikan functors sebagai argumen ke fungsi lain seperti std :: transform atau algoritma pustaka standar lainnya. Anda dapat melakukan hal yang sama dengan pointer fungsi biasa kecuali, seperti yang saya katakan di atas, functors dapat "dikustomisasi" karena mengandung keadaan, membuatnya lebih fleksibel (Jika saya ingin menggunakan pointer fungsi, saya harus menulis sebuah fungsi yang menambahkan tepat 1 ke argumennya.Functor adalah umum, dan menambahkan apa pun yang Anda inisialisasi dengan), dan mereka juga berpotensi lebih efisien. Dalam contoh di atas, kompiler tahu persis fungsi mana yang std::transformharus dipanggil. Seharusnya menelepon add_x::operator(). Itu berarti dapat inline panggilan fungsi itu. Dan itu membuatnya sama efisiennya dengan jika saya secara manual memanggil fungsi pada setiap nilai vektor.

Jika saya telah melewatkan pointer fungsi, kompiler tidak dapat langsung melihat fungsi yang ditunjuknya, jadi kecuali ia melakukan beberapa optimasi global yang cukup kompleks, ia harus melakukan dereferensi pointer pada saat runtime, dan kemudian melakukan panggilan.

jalf
sumber
32
Bisakah Anda menjelaskan baris ini, silakan std :: transform (in.begin (), in.end (), out.begin (), add_x (1)); mengapa Anda menulis di sana add_x, bukan add42?
Alecs
102
@Alec Keduanya akan bekerja (tetapi efeknya akan berbeda). Jika saya menggunakan add42, saya akan menggunakan functor yang saya buat sebelumnya, dan menambahkan 42 untuk setiap nilai. Dengan add_x(1)saya membuat instance baru dari functor, yang hanya menambahkan 1 untuk setiap nilai. Ini hanya untuk menunjukkan bahwa sering, Anda instantiate functor "on the fly", ketika Anda membutuhkannya, daripada membuatnya terlebih dahulu, dan menyimpannya sebelum Anda benar-benar menggunakannya untuk apa pun.
jalf
8
@zadane tentu saja. Mereka hanya harus memiliki operator(), karena itulah yang digunakan penelepon untuk memintanya. Apa lagi fungsi functor anggota, konstruktor, operator dan variabel anggota sepenuhnya terserah Anda.
Jalf
4
@ rikimaru2013 Dalam bahasa pemrograman fungsional, Anda benar, suatu fungsi juga merupakan functor, tetapi dalam bahasa C ++, functor secara khusus merupakan kelas yang digunakan sebagai fungsi. Terminologi itu agak disalahgunakan sejak awal, tetapi pembagian ini adalah perbedaan yang berguna dan masih ada sampai sekarang. Jika Anda mulai menyebut fungsi sebagai "functors" dalam konteks C ++ maka Anda hanya akan membingungkan percakapan.
srm
6
Apakah ini kelas atau turunan dari kelas? Dalam sebagian besar sumber, add42akan disebut sebagai functor, bukan add_x(yang merupakan kelas dari functor atau hanya kelas functor). Saya menemukan bahwa terminologi konsisten karena functors juga disebut objek fungsi , bukan kelas fungsi. Bisakah Anda mengklarifikasi hal ini?
Sergei Tachenov
121

Selain sedikit. Anda dapat menggunakan boost::function, untuk membuat functors dari fungsi dan metode, seperti ini:

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"

dan Anda dapat menggunakan boost :: bind untuk menambahkan status ke functor ini

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"

dan yang paling berguna, dengan fungsi boost :: bind and boost :: Anda dapat membuat functor dari metode kelas, sebenarnya ini adalah delegasi:

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"

Anda dapat membuat daftar atau vektor functors

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

Ada satu masalah dengan semua hal ini, pesan kesalahan kompilator tidak dapat dibaca manusia :)

Evgeny Lazin
sumber
4
Tidak operator ()boleh publik dalam contoh pertama Anda karena kelas default untuk pribadi?
NathanOliver
4
mungkin pada titik tertentu jawaban ini layak diperbarui, karena sekarang lambdas adalah cara termudah untuk mendapatkan functor dari apa pun
idclev 463035818
102

Functor adalah objek yang bertindak seperti fungsi. Pada dasarnya, kelas yang mendefinisikan operator().

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

MyFunctor doubler;
int x = doubler(5);

Keuntungan sebenarnya adalah bahwa functor dapat memegang status.

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)
{ ....}
James Curran
sumber
11
Hanya perlu menambahkan bahwa mereka dapat digunakan seperti pointer fungsi.
Martin York
7
@LokiAstari - Bagi yang baru mengenal konsep ini, itu bisa sedikit menyesatkan. Functors dapat "digunakan seperti", tetapi tidak selalu "di tempat" pointer fungsi. Misalnya, fungsi yang mengambil pointer fungsi tidak bisa mengambil functor di tempatnya bahkan jika functor memiliki argumen yang sama dan mengembalikan nilai sebagai pointer fungsi. Tetapi pada umumnya ketika mendesain, functors adalah cara yang disukai dan secara teoritis "lebih modern" untuk pergi.
MasonWinsauer
Mengapa yang kedua kembali intpadahal seharusnya kembali bool? Ini adalah C ++, bukan C. Ketika jawaban ini ditulis, apakah booltidak ada?
Dana Gugatan Monica
@ QPaysTaxes Kesalahan ketik saya kira. Saya mungkin menyalin kode dari contoh pertama dan lupa untuk mengubahnya. Saya sudah memperbaikinya sekarang.
James Curran
1
@Riasat Jika Matcher ada di perpustakaan, mendefinisikan Is5 () cukup sederhana. Dan Anda dapat membuat Is7 (), Is32 () dll. Selanjutnya, itu hanya sebuah contoh. Fungsinya bisa jauh lebih rumit.
James Curran
51

Nama "functor" telah secara tradisional digunakan dalam teori kategori jauh sebelum C ++ muncul di tempat kejadian. Ini tidak ada hubungannya dengan konsep C ++ dari functor. Lebih baik menggunakan objek fungsi nama daripada apa yang kita sebut "functor" di C ++. Ini adalah bagaimana bahasa pemrograman lain memanggil konstruksi yang sama.

Digunakan sebagai ganti fungsi biasa:

Fitur:

  • Objek fungsi mungkin memiliki status
  • Objek fungsi cocok dengan OOP (berperilaku seperti setiap objek lainnya).

Cons:

  • Membawa lebih banyak kompleksitas ke program.

Digunakan alih-alih fungsi pointer:

Fitur:

  • Objek fungsi sering dapat digarisbawahi

Cons:

  • Objek fungsi tidak dapat ditukar dengan tipe objek fungsi lainnya selama runtime (setidaknya kecuali jika ia memperpanjang beberapa kelas dasar, yang karenanya memberikan beberapa overhead)

Digunakan sebagai ganti fungsi virtual:

Fitur:

  • Objek fungsi (non-virtual) tidak memerlukan pengiriman vtable dan runtime, sehingga lebih efisien dalam banyak kasus

Cons:

  • Objek fungsi tidak dapat ditukar dengan tipe objek fungsi lainnya selama runtime (setidaknya kecuali jika ia memperpanjang beberapa kelas dasar, yang karenanya memberikan beberapa overhead)
dokter
sumber
1
Bisakah Anda jelaskan use case ini dalam contoh nyata? bagaimana kita dapat menggunakan functors sebagai polimorfisme dan pointer?
Milad Khajavi
1
Apa yang sebenarnya berarti bahwa functor memegang status?
erogol
terima kasih telah menunjukkan bahwa seseorang memerlukan kelas dasar untuk memiliki semacam polimorfisme. Saya hanya punya masalah yang saya harus menggunakan functor di tempat yang sama dengan fungsi pointer sederhana dan satu-satunya cara saya temukan adalah menulis kelas dasar functor (karena saya tidak bisa menggunakan C ++ 11 hal). Tidak yakin apakah overhead ini masuk akal sampai saya membaca jawaban Anda.
idclev 463035818
1
@Erogol functor adalah objek yang terjadi untuk mendukung sintaks foo(arguments). Oleh karena itu, dapat berisi variabel; misalnya, jika Anda memiliki update_password(string)fungsi, Anda mungkin ingin melacak seberapa sering itu terjadi; dengan functor, yang dapat private long timemewakili cap waktu yang terakhir terjadi. Dengan pointer fungsi atau fungsi polos, Anda akan perlu menggunakan luar variabel namespace, yang hanya berhubungan langsung dengan dokumentasi dan penggunaan, bukan oleh definition.l
Dana Monica Gugatan
4
⁺¹ karena menyebutkan bahwa nama itu dibuat tanpa alasan. Saya baru saja mencari apa hubungan antara matematika (atau fungsional jika Anda mau) functor dan yang dari C ++.
Hi-Angel
41

Seperti yang telah disebutkan orang lain, functor adalah objek yang bertindak seperti fungsi, yaitu overload operator fungsi panggilan.

Functors biasanya digunakan dalam algoritma STL. Mereka berguna karena mereka dapat menahan keadaan sebelum dan di antara panggilan fungsi, seperti penutupan dalam bahasa fungsional. Misalnya, Anda bisa mendefinisikan MultiplyByfunctor yang mengalikan argumennya dengan jumlah yang ditentukan:

class MultiplyBy {
private:
    int factor;

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

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

Kemudian Anda bisa meneruskan MultiplyByobjek ke algoritma seperti std :: transform:

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

Keuntungan lain dari functor atas penunjuk ke suatu fungsi adalah bahwa panggilan dapat digarisbawahi dalam lebih banyak kasus. Jika Anda melewati fungsi pointer ke transform, kecuali bahwa panggilan mendapat inline dan compiler tahu bahwa Anda selalu melewati fungsi yang sama untuk itu, tidak dapat inline panggilan melalui pointer.

Matthew Crumley
sumber
37

Untuk pemula seperti saya di antara kita: setelah sedikit riset saya menemukan apa yang dilakukan oleh kode jalf.

Functor adalah objek kelas atau struct yang dapat "dipanggil" seperti fungsi. Ini dimungkinkan dengan overloading () operator. The () operator(tidak yakin apa yang disebut) dapat mengambil sejumlah argumen. Operator lain hanya mengambil dua yaitu + operatorhanya dapat mengambil dua nilai (satu di setiap sisi operator) dan mengembalikan nilai apa pun yang Anda miliki kelebihannya. Anda dapat memasukkan sejumlah argumen di dalam () operatorapa yang memberikan fleksibilitas.

Untuk membuat functor, pertama Anda membuat kelas Anda. Kemudian Anda membuat konstruktor ke kelas dengan parameter pilihan jenis dan nama. Ini diikuti dalam pernyataan yang sama dengan daftar penginisialisasi (yang menggunakan satu operator titik dua, sesuatu yang saya juga baru) yang membangun objek anggota kelas dengan parameter yang dinyatakan sebelumnya ke konstruktor. Kemudian () operatorkelebihan beban. Akhirnya Anda mendeklarasikan objek pribadi dari kelas atau struct yang telah Anda buat.

Kode saya (saya menemukan nama variabel jalf membingungkan)

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

Jika semua ini tidak akurat atau hanya salah, jangan ragu untuk mengoreksi saya!

Johanne Irish
sumber
1
Operator () disebut operator fungsi-panggilan. Saya kira Anda juga bisa menyebutnya operator kurung.
Gautam
4
"Parameter ini sebenarnya argumen" parameterVar "yang disahkan oleh konstruktor yang baru saja kita tulis" Hah?
Lightness Races in Orbit
22

Functor adalah fungsi orde tinggi yang menerapkan fungsi untuk tipe parametrized (yaitu templated). Ini adalah generalisasi dari fungsi peta tingkat tinggi. Sebagai contoh, kita dapat mendefinisikan functor untuk std::vectorseperti ini:

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

Fungsi ini mengambil std::vector<T>dan mengembalikan std::vector<U>ketika diberi fungsi Fyang mengambil Tdan mengembalikan a U. Sebuah functor tidak harus didefinisikan lebih dari tipe kontainer, itu dapat didefinisikan untuk tipe templated juga, termasuk 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)));
}

Inilah contoh sederhana yang mengubah tipe menjadi 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);

Ada dua hukum yang harus diikuti oleh para pelaku. Yang pertama adalah hukum identitas, yang menyatakan bahwa jika functor diberikan fungsi identitas, itu harus sama dengan menerapkan fungsi identitas ke tipe, yaitu fmap(identity, x)harus sama dengan 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);

Hukum berikutnya adalah hukum komposisi, yang menyatakan bahwa jika functor diberi komposisi dua fungsi, itu harus sama dengan menerapkan functor untuk fungsi pertama dan sekali lagi untuk fungsi kedua. Jadi, fmap(std::bind(f, std::bind(g, _1)), x)harus sama dengan 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));
Paul Fultz II
sumber
2
Pasal berdebat functor yang harus benar digunakan untuk makna ini (lihat juga en.wikipedia.org/wiki/Functor ), dan bahwa menggunakannya untuk fungsi objek hanya ceroboh: jackieokay.com/2017/01/26/functors.html Ini mungkin sudah terlambat untuk itu, mengingat jumlah jawaban di sini yang hanya mempertimbangkan fungsi objek artinya.
armb
2
Jawaban ini adalah jawaban dengan> 700 Upvotes. Sebagai seseorang yang tahu Haskell lebih baik daripada C ++, bahasa C ++ lingua membuatku bingung sepanjang waktu.
mschmidt
Teori kategori dan C ++? Apakah ini akun rahasia Bartosz Milewski?
Mateen Ulhaq
1
Mungkin bermanfaat untuk meringkas undang-undang functor dalam notasi standar: fmap(id, x) = id(x)dan fmap(f ◦ g, x) = fmap(f, fmap(g, x)).
Mateen Ulhaq
@mschmidt sementara functor juga berarti ini, C ++ kelebihan nama berarti sama dengan "objek fungsi"
Caleth
9

Inilah situasi aktual di mana saya dipaksa menggunakan Functor untuk menyelesaikan masalah saya:

Saya memiliki serangkaian fungsi (katakanlah, 20 di antaranya), dan semuanya identik, kecuali masing-masing memanggil fungsi spesifik yang berbeda di 3 titik tertentu.

Ini adalah pemborosan yang luar biasa, dan duplikasi kode. Biasanya saya hanya akan melewatkan fungsi pointer, dan sebut saja di 3 tempat. (Jadi kodenya hanya perlu muncul sekali, bukan dua puluh kali.)

Tetapi kemudian saya menyadari, dalam setiap kasus, fungsi spesifik membutuhkan profil parameter yang sama sekali berbeda! Terkadang 2 parameter, terkadang 5 parameter, dll.

Solusi lain akan memiliki kelas dasar, di mana fungsi spesifik adalah metode yang ditimpa dalam kelas turunan. Tetapi apakah saya benar-benar ingin membangun semua Warisan ini, supaya saya dapat melewatkan fungsi pointer ????

SOLUSI: Jadi yang saya lakukan adalah, saya membuat kelas pembungkus ("Functor") yang dapat memanggil salah satu fungsi yang saya butuhkan dipanggil. Saya mengaturnya terlebih dahulu (dengan parameternya, dll) dan kemudian saya meneruskannya sebagai ganti fungsi pointer. Sekarang kode yang dipanggil dapat memicu Functor, tanpa mengetahui apa yang terjadi di dalam. Ia bahkan dapat memanggilnya beberapa kali (saya membutuhkannya untuk menelepon 3 kali.)


Itu saja - contoh praktis di mana Functor ternyata menjadi solusi yang jelas dan mudah, yang memungkinkan saya untuk mengurangi duplikasi kode dari 20 fungsi menjadi 1.

Teman Seperjalanan
sumber
3
Jika functor Anda memanggil fungsi spesifik yang berbeda, dan fungsi-fungsi lain ini bervariasi dalam jumlah parameter yang mereka terima, apakah ini berarti functor Anda menerima sejumlah variabel argumen untuk mengirim ke fungsi-fungsi lain ini?
johnbakers
4
bisa tolong jelaskan skenario di atas dengan mengutip beberapa bagian kode, saya baru mengenal c ++ ingin memahami konsep ini ..
sanjeev
3

Kecuali untuk digunakan dalam panggilan balik, fungsi C ++ juga dapat membantu menyediakan gaya akses yang disukai Matlab ke kelas matriks . Ada sebuah contoh .

Yantao Xie
sumber
Ini (contoh matriks) adalah penggunaan biasa operator()tetapi tidak memanfaatkan properti fungsi objek.
Renardesque
3

Seperti telah diulang, functors adalah kelas yang dapat diperlakukan sebagai fungsi (operator overload ()).

Mereka paling berguna untuk situasi di mana Anda perlu mengaitkan beberapa data dengan panggilan berulang atau tertunda ke suatu fungsi.

Misalnya, daftar fungsi yang ditautkan dapat digunakan untuk mengimplementasikan sistem koroutin sinkron overhead rendah dasar, dispatcher tugas, atau penguraian file yang interruptable. Contoh:

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

Tentu saja, contoh-contoh ini tidak begitu berguna dalam diri mereka. Mereka hanya menunjukkan bagaimana functors dapat berguna, functors itu sendiri sangat mendasar dan tidak fleksibel dan ini membuat mereka kurang bermanfaat daripada, misalnya, apa yang disediakan boost.

nfries88
sumber
2

Functors digunakan dalam gtkmm untuk menghubungkan beberapa tombol GUI ke fungsi atau metode C ++ yang sebenarnya.


Jika Anda menggunakan pthread library untuk membuat aplikasi Anda multithreaded, Functors dapat membantu Anda.
Untuk memulai utas, salah satu argumen pthread_create(..)adalah penunjuk fungsi yang akan dieksekusi pada utasnya sendiri.
Tapi ada satu ketidaknyamanan. Pointer ini tidak bisa menjadi pointer ke metode, kecuali metode statis , atau kecuali Anda menentukan kelasnya , seperti class::method. Dan satu hal lagi, antarmuka metode Anda hanya dapat:

void* method(void* something)

Jadi Anda tidak dapat menjalankan (dengan cara yang jelas dan sederhana), metode dari kelas Anda di utas tanpa melakukan sesuatu yang ekstra.

Cara yang sangat baik untuk berurusan dengan utas di C ++, adalah membuat Threadkelas Anda sendiri . Jika Anda ingin menjalankan metode dari MyClasskelas, apa yang saya lakukan adalah, ubah metode tersebut menjadi Functorkelas turunan.

Juga, Threadkelas memiliki metode ini: static void* startThread(void* arg)
Penunjuk ke metode ini akan digunakan sebagai argumen untuk memanggil pthread_create(..). Dan apa yang startThread(..)harus diterima dalam arg adalah void*referensi yang dicor ke instance di heap dari setiap Functorkelas turunan, yang akan dilemparkan kembali ke Functor*saat dieksekusi, dan kemudian disebut run()metode itu.

Erandros
sumber
2

Untuk menambahkan, saya telah menggunakan objek fungsi agar sesuai dengan metode warisan yang ada dengan pola perintah; (satu-satunya tempat di mana keindahan paradigma OO benar OCP saya rasakan); Juga menambahkan di sini pola adaptor fungsi terkait.

Misalkan metode Anda memiliki tanda tangan:

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

Kita akan melihat bagaimana kita dapat menyesuaikannya dengan pola Command - untuk ini, pertama, Anda harus menulis adaptor fungsi anggota sehingga dapat dipanggil sebagai objek fungsi.

Catatan - ini jelek, dan mungkin Anda bisa menggunakan Boost bind helper dll, tetapi jika Anda tidak bisa atau tidak mau, ini adalah satu cara.

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

Juga, kita membutuhkan metode helper mem_fun3 untuk kelas di atas untuk membantu dalam memanggil.

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

}

Sekarang, untuk mengikat parameter, kita harus menulis fungsi binder. Jadi, ini dia:

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

Dan, fungsi pembantu untuk menggunakan kelas 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);
}

Sekarang, kita harus menggunakan ini dengan kelas Command; gunakan typedef berikut:

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

Ini adalah bagaimana Anda menyebutnya:

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

Catatan: f3 (); akan memanggil metode task1-> ThreeParameterTask (21,22,23) ;.

Konteks penuh dari pola ini pada tautan berikut

Alex Punnen
sumber
2

Keuntungan besar dari penerapan fungsi sebagai functors adalah mereka dapat mempertahankan dan menggunakan kembali keadaan di antara panggilan. Sebagai contoh, banyak algoritma pemrograman dinamis, seperti algoritma Wagner-Fischer untuk menghitung jarak Levenshtein antara string, bekerja dengan mengisi tabel besar hasil. Sangat tidak efisien untuk mengalokasikan tabel ini setiap kali fungsi dipanggil, jadi mengimplementasikan fungsi sebagai functor dan menjadikan tabel sebagai variabel anggota dapat sangat meningkatkan kinerja.

Di bawah ini adalah contoh penerapan algoritma Wagner-Fischer sebagai functor. Perhatikan bagaimana tabel dialokasikan di konstruktor, dan kemudian digunakan kembali operator(), dengan mengubah ukuran sesuai kebutuhan.

#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];
    }
};
Martin Broadhurst
sumber
1

Functor juga dapat digunakan untuk mensimulasikan mendefinisikan fungsi lokal dalam suatu fungsi. Lihat pertanyaan dan lainnya .

Tetapi functor lokal tidak dapat mengakses variabel otomatis di luar. Fungsi lambda (C ++ 11) adalah solusi yang lebih baik.

Yantao Xie
sumber
-10

Saya telah "menemukan" penggunaan functors yang sangat menarik: Saya menggunakannya ketika saya tidak memiliki nama yang bagus untuk satu metode, karena functor adalah metode tanpa nama ;-)

JChMathae
sumber
Mengapa Anda menggambarkan functor sebagai "metode tanpa nama"?
Anderson Green
5
Fungsi tanpa nama disebut lambda.
Paul Fultz II