C ++ functorları ve kullanımları nelerdir?


876

C ++ 'daki işlevler hakkında çok şey duyuyorum. Birisi bana ne olduklarına ve hangi durumlarda yararlı olacağına dair genel bir bakış verebilir mi?



2
C ++ 'da bir kapatma oluşturmak için kullanılır.
bakır.

Aşağıdaki yanıtlara bakıldığında, birisi ne anlama operator()(...)geldiğini merak ediyorsa : "işlev çağrısı" operatörünü aşırı yüklüyor. Sadece operatör için aşırı operatör yüküdür (). operator()Denilen bir işlevi çağırırken hata yapmayın operator, ancak normal operatör aşırı yükleme sözdizimi olarak görün.
zardosht

Yanıtlar:


1041

Bir functor hemen hemen operatörü () tanımlayan bir sınıftır. Bu, bir işleve "benzeyen" nesneler oluşturmanıza olanak tanır:

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

Functors hakkında birkaç güzel şey var. Birincisi, normal işlevlerden farklı olarak, devlet içerebilirler. Yukarıdaki örnek, verdiğiniz her şeye 42 ekleyen bir işlev oluşturur. Ancak bu 42 değeri kodlanmış değil, işlev örneğimizi oluşturduğumuzda yapıcı argümanı olarak belirtildi. Yapıcıyı farklı bir değerle çağırarak 27 ekleyen başka bir toplayıcı oluşturabilirim. Bu onları güzelleştirilebilir hale getirir.

Son satırların gösterdiği gibi, functors'ı genellikle std :: transform veya diğer standart kütüphane algoritmaları gibi diğer işlevlere argüman olarak iletirsiniz. Normal bir işlev işaretçisi ile aynı şeyi yapabilirsiniz, ancak yukarıda söylediğim gibi, işlevler durum içerdiğinden daha esnek hale getirdikleri için işlevler "özelleştirilebilir" (Bir işlev işaretçisi kullanmak istersem, bir işlev yazmak zorunda kalırdım) Functor geneldir ve başlattığınız her şeyi ekler) ve potansiyel olarak daha verimlidir. Yukarıdaki örnekte, derleyici tam olarak hangi işlevi std::transformçağırması gerektiğini bilir . Aramalı add_x::operator(). Bu, işlev çağrısını satır içine alabileceği anlamına gelir. Ve bu, vektörün her bir değerinde işlevi manuel olarak çağırmışım gibi verimli hale getirir.

Bunun yerine bir işlev işaretçisi geçmiş olsaydım, derleyici hangi işleve işaret ettiğini hemen göremezdi, bu yüzden oldukça karmaşık küresel optimizasyonlar gerçekleştirmedikçe, işaretçiyi çalışma zamanında geri çağırmak ve sonra çağrı yapmak zorunda kalacaktı.


32
Bu satırı açıklayabilir misiniz, lütfen std :: transform (in.begin (), in.end (), out.begin (), add_x (1)); neden add42 değil, add_x yazıyorsunuz?
Alecs

102
@Alecs Her ikisi de işe yarardı (ancak etki farklı olurdu). Eğer add42kullansaydım, daha önce oluşturduğum işlevi kullanırdım ve her bir değere 42 eklerdim. İle add_x(1)I funktor, sadece her bir değer 1 ekler birinin yeni bir örneğini oluşturur. Basitçe, sıkça, ilk önce oluşturmak yerine ve her şey için kullanmadan önce onu tutmak yerine, ihtiyaç duyduğunuzda "anında" başlattığınızı göstermek içindir.
jalf

8
@zadane tabii ki. Sadece sahip olmak zorundalar operator(), çünkü arayan onu çağırmak için kullanır. Ne başka funktoru üye fonksiyonları, yapıcılar, operatörler ve üye değişkenleri vardır size tamamen doldu.
jalf

4
@ rikimaru2013 Fonksiyonel programlamanın başlangıcında, haklısınız, bir fonksiyon aynı zamanda bir fonksiyonerdir, ancak C ++ bakış açısıyla, fonksiyoner özellikle fonksiyon olarak kullanılan bir sınıftır. Terminoloji erken kötüye kullanıldı, ancak bölünme faydalı bir ayrımdır ve bugün de devam etmektedir. Bir C ++ bağlamında işlevlere "functors" olarak başvurmaya başlarsanız, konuşmayı karıştırmanız yeterlidir.
sr

6
Bir sınıf mı yoksa sınıfın bir örneği mi? Çoğu kaynakta add42functor olarak adlandırılır add_x(functor sınıfı veya functor sınıfıdır). Terminolojiyi tutarlı buluyorum çünkü functorlara fonksiyon sınıfları değil, fonksiyon nesneleri de denir . Bu noktayı açıklığa kavuşturabilir misiniz?
Sergei Tachenov

121

Küçük ek. Aşağıdaki boost::functiongibi işlev ve yöntemlerden functor oluşturmak için kullanabilirsiniz :

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"

ve bu işleve durum eklemek için boost :: bind komutunu kullanabilirsiniz

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"

ve en yararlı, boost :: bind and boost :: işlevi ile sınıf yönteminden functor oluşturabilirsiniz, aslında bu bir temsilci:

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"

İşlev listesi veya vektör oluşturabilirsiniz

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

Tüm bu şeyler ile ilgili bir sorun var, derleyici hata mesajları insan tarafından okunabilir değil :)


4
operator ()Sınıflar varsayılan olarak gizli olduğundan ilk örneğinizde herkese açık olmamalısınız ?
NathanOliver

4
belki bir noktada bu cevap bir güncellemeyi hak ediyor, çünkü lambdas her ne olursa olsun bir functor almanın en kolay yolu
idclev 463035818

102

Functor, bir işlev gibi davranan bir nesnedir. Temel olarak, tanımlayan bir sınıf operator().

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

MyFunctor doubler;
int x = doubler(5);

Asıl avantaj, bir işlevin durumu tutabilmesidir.

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
Sadece bir işlev işaretçisi gibi kullanılabileceğini eklemeniz yeterlidir.
Martin York

7
@LokiAstari - Konseptte yeni olanlar için bu biraz yanıltıcı olabilir. Functors "gibi" kullanılabilir, ancak her zaman işlev işaretçileri yerine "kullanılamaz". Örneğin, işlev işaretçisi alan bir işlev, işlev işaretçisi ile aynı bağımsız değişkenlere ve dönüş değerine sahip olsa bile yerine bir işlev alamaz. Ancak, tasarım yaparken, functorlar tercih edilen ve teorik olarak "daha modern" bir yol.
MasonWinsauer

İkincisi neden geri dönmesi intgerektiğinde geri dönüyor bool? Bu C ++, C değil. Bu cevap yazıldığı zaman yoktu bool?
Monica'nın Davası

@ QPaysTaxes Sanırım bir yazım hatası. Muhtemelen ilk örnekten kodu kopyalamamış ve değiştirmeyi unuttum. Şimdi düzelttim.
James Curran

1
@Riasat Matcher bir kütüphanedeyse, Is5 () öğesini tanımlamak oldukça basittir. Ayrıca Is7 (), Is32 () vb. Oluşturabilirsiniz. Dahası, bu sadece bir örnek. Fonksiyonel çok daha karmaşık olabilir.
James Curran

51

"Functor" adı geleneksel olarak kategori teorisinde C ++ sahneye çıkmadan çok önce kullanılmıştır . Bunun C ++ functor kavramı ile ilgisi yoktur. C ++ 'da "functor" dediğimiz yerine name işlev nesnesini kullanmak daha iyidir . Diğer programlama dilleri benzer yapıları bu şekilde adlandırır.

Düz işlev yerine kullanılır:

Özellikleri:

  • İşlev nesnesinin durumu olabilir
  • İşlev nesnesi OOP'ye sığar (diğer tüm nesneler gibi davranır).

Eksileri:

  • Programa daha fazla karmaşıklık getirir.

İşlev işaretçisi yerine kullanılır:

Özellikleri:

  • İşlev nesnesi genellikle satır içine alınabilir

Eksileri:

  • İşlev nesnesi çalışma zamanı sırasında diğer işlev nesnesi türüyle değiştirilemez (en azından bazı temel sınıfları genişletmediği ve bu nedenle bazı ek yükler verdiği sürece)

Sanal işlev yerine kullanılır:

Özellikleri:

  • İşlev nesnesi (sanal olmayan), vtable ve çalışma zamanı gönderme gerektirmez, bu nedenle çoğu durumda daha verimlidir

Eksileri:

  • İşlev nesnesi çalışma zamanı sırasında diğer işlev nesnesi türüyle değiştirilemez (en azından bazı temel sınıfları genişletmediği ve bu nedenle bazı ek yükler verdiği sürece)

1
Bu kullanım örneğini gerçek örnekte açıklayabilir misiniz? functor'ları polimorfizm ve işlev işaretçisi olarak nasıl kullanabiliriz?
Milad Khajavi

1
Aslında bir işlevin devlet tutması ne anlama gelir?
erogol

bir çeşit polimorfizme sahip olmak için bir temel sınıfa ihtiyaç duyduğuna işaret ettiğiniz için teşekkür ederiz. Ben sadece basit bir işlev işaretçisi ile aynı yerde bir functor kullanmak zorunda sorun var ve bulduğum tek yolu (C ++ 11 şeyler kullanamıyorum gibi) bir functor temel sınıf yazmak oldu. Cevabınızı okuyana kadar bu yükün mantıklı olup olmadığından emin değildim.
idclev 463035818

1
@Erogol Bir işlev, sözdizimini destekleyen bir nesnedir foo(arguments). Bu nedenle değişken içerebilir; örneğin, bir update_password(string)işleviniz varsa, bunun ne sıklıkta gerçekleştiğini takip etmek isteyebilirsiniz; bir functor ile, private long timebu son gerçekleştiği zaman damgasını temsil eden olabilir . Bir işlev işaretçisi veya düz işlevle, ad alanının dışında, tanım yerine yalnızca belge ve kullanımla doğrudan ilişkili olan bir değişken kullanmanız gerekir.
L

4
The adın sebepsiz yere konulduğunu belirtmek için. Ben sadece matematiksel (ya da isterseniz fonksiyonel) functor ve C ++ bir arasındaki ilişki ne arıyor .
Hi-Angel

41

Diğerlerinin de belirttiği gibi, bir işlev bir işlev gibi davranan bir nesnedir, yani işlev çağrısı işlecini aşırı yükler.

Functor'lar STL algoritmalarında yaygın olarak kullanılır. İşlevsel dillerin kapanması gibi işlev çağrılarının öncesinde ve arasında durumu tutabildikleri için kullanışlıdırlar. Örneğin, MultiplyBybağımsız değişkenini belirtilen bir miktarla çarpan bir işlev tanımlayabilirsiniz :

class MultiplyBy {
private:
    int factor;

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

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

Sonra bir MultiplyBynesneyi std :: transform gibi bir algoritmaya geçirebilirsiniz :

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

Bir işlevin bir işaretçiye göre bir işlevinin bir diğer avantajı da çağrının daha fazla durumda satır içi yapılabilmesidir. Bir işlev işaretçisini geçtiyseniz transform, bu çağrı satır içine alınmazsa ve derleyici her zaman aynı işlevi ona aktardığınızı bilmiyorsa, çağrıyı işaretçiden satır içine alamaz.


37

Aramızdaki benim gibi yeni başlayanlar için: küçük bir araştırmadan sonra jalf kodunun ne yaptığını anladım.

Bir işlev, bir işlev gibi "çağrılabilecek" bir sınıf veya yapı nesnesidir. Bu, aşırı yüklenerek mümkün olur () operator. () operator(Değil emin onun dediği) herhangi bir sayıda argüman alabilir. Diğer operatörler sadece iki tane alır, yani + operatorsadece iki değer alabilir (operatörün her iki tarafında bir tane) ve aşırı yüklediğiniz değeri döndürür. () operatorEsnekliğini sağlayan herhangi bir sayıda argümanın içine sığabilirsiniz .

Önce bir işlev oluşturmak için sınıfınızı oluşturursunuz. Daha sonra sınıfa, seçtiğiniz tür ve adda bir parametre ile bir kurucu oluşturursunuz. Bunu, aynı ifadede, daha önce bildirilen parametreyle sınıf üyesi nesnelerini kurucuya oluşturan bir başlatıcı listesi (tek bir iki nokta operatörünü, ayrıca yeni bir şey kullanan) izler. Sonra () operatoraşırı yüklenir. Son olarak, oluşturduğunuz sınıfın veya yapının özel nesnelerini beyan edersiniz.

Kodum (jalf değişken isimlerini kafa karıştırıcı buldum)

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

Bunlardan herhangi biri yanlışsa veya sadece yanlışsa beni düzeltmekten çekinmeyin!


1
() Operatörüne işlev çağırma operatörü denir. Sanırım buna parantez operatörü de diyebilirsiniz.
Gautam

4
"Bu parametre aslında biz sadece" Huh?
Yörüngedeki Hafiflik Yarışları

22

Bir işlev, parametrelenmiş (yani şablonlanmış) türlere bir işlev uygulayan üst düzey bir işlevdir. Harita üst düzey işlevinin genelleştirilmesidir . Örneğin, bunun için bir işlev tanımlayabiliriz 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;
}

Bu işlev a alır std::vector<T>ve std::vector<U>a Falan bir işlev verildiğinde Tgeri döner ve a döndürür U. Bir functor'ın konteyner türleri üzerinde tanımlanması gerekmez, aşağıdakiler de dahil olmak üzere herhangi bir şablonlanmış tip için tanımlanabilir 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)));
}

Heres, türü a'ya dönüştüren basit bir örnek 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);

Fonksiyonerlerin uyması gereken iki yasa vardır. İlk funktor bir kimlik işlevi verilirse, o olduğunu, tipine kimlik işlevi uygulamakla aynı olması gerektiğini bildiren kimlik kanunu olduğu fmap(identity, x)aynı olmalıdır 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);

Bir sonraki yasa, eğer fonksiyona iki fonksiyondan oluşan bir kompozisyon verilirse, fonksiyonun ilk fonksiyon için ve sonra tekrar ikinci fonksiyon için uygulanmasıyla aynı olması gerektiğini belirten kompozisyon yasasıdır. Yani, fmap(std::bind(f, std::bind(g, _1)), x)aynı olmalı 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
O functor savunarak Madde doğru (ayrıca bkz bu anlamı için kullanılması gereken en.wikipedia.org/wiki/Functor ) ve fonksiyon nesneler için kullanmaya sadece özensiz olduğunu: jackieokay.com/2017/01/26/functors.html It burada sadece işlev nesnesinin anlamını dikkate alan cevapların sayısı göz önüne alındığında, bunun için çok geç olabilir.
armb

2
Bu cevap> 700 Upvotes'a sahip olmalıdır. Haskell'i C ++ 'dan daha iyi tanıyan biri olarak, C ++ lingua beni her zaman şaşırttı.
mschmidt

Kategori teorisi ve C ++? Bu Bartosz Milewski'nin gizli SO hesabı mı?
Mateen Ulhaq

1
İşlev yasalarını standart gösterimde özetlemek yararlı olabilir: fmap(id, x) = id(x)ve fmap(f ◦ g, x) = fmap(f, fmap(g, x)).
Mateen Ulhaq

@mschmidt functor da bu anlama gelirken, C ++ adı "işlev nesnesi" ile aynı anlama gelir.
Caleth

9

Sorunumu çözmek için bir Functor kullanmaya zorlandığım gerçek bir durum:

Bir dizi işlevim var (diyelim ki bunlardan 20 tanesi) ve hepsi aynıdır, ancak her biri 3 belirli noktada farklı bir özel işlevi çağırır.

Bu inanılmaz bir israf ve kod tekrarı. Normalde sadece bir fonksiyon göstergesini geçirdim ve sadece 3 noktada çağırıyorum. (Bu nedenle kodun yirmi kez değil, yalnızca bir kez görünmesi gerekir.)

Ama sonra, her durumda, belirli fonksiyonun tamamen farklı bir parametre profili gerektirdiğini fark ettim! Bazen 2 parametre, bazen 5 parametre vb.

Başka bir çözüm, belirli bir işlevin türetilmiş bir sınıfta geçersiz kılınmış bir yöntem olduğu bir temel sınıfa sahip olmak olacaktır. Ama gerçekten bu mirasın tamamını oluşturmak istiyor muyum, sadece bir fonksiyon göstergesini geçebiliyorum ????

ÇÖZÜM: Yaptığım şey, ihtiyaç duyduğum işlevlerden herhangi birini çağırabilen bir sarmalayıcı sınıfı ("Functor") yaptım. Önceden ayarladım (parametreleri vb. İle) ve sonra bir işlev işaretçisi yerine geçiriyorum. Artık aranan kod, içeride ne olduğunu bilmeden Functor'u tetikleyebilir. Hatta birden fazla kez çağırabilir (3 kez aramak için ihtiyacım vardı.)


İşte bu - bir Functor'un 20 fonksiyondan 1'e kod çoğaltmasını azaltmama izin veren bariz ve kolay bir çözüm olduğu pratik bir örnek.


3
Functor'ınız farklı belirli işlevler çağırdıysa ve bu diğer işlevler kabul ettikleri parametre sayısına göre değiştiyse, bu, functor'ınızın bu diğer işlevlere göndermek için değişken sayıda argümanı kabul ettiği anlamına mı geliyor?
johnbakers

4
kodun bir kısmını alıntılayarak yukarıdaki senaryo açıklayabilir misiniz, ben c ++ bu kavramı anlamak istiyorum yeni ..
sanjeev

3

Geri aramada kullanılanlar dışında, C ++ işlevleri bir matris sınıfına erişim tarzını seven bir Matlab sağlamaya yardımcı olabilir . Bir örnek var .


Bu (matris örneği) operator()işlev nesnesi özelliklerinin basit kullanımıdır, ancak işlev nesnesi özelliklerini kullanmaz.
renardesque

3

Tekrarlanan gibi, functorlar fonksiyon olarak değerlendirilebilecek sınıflardır (aşırı yük operatörü ()).

Bunlar, bazı verileri bir işleve tekrarlanan veya gecikmeli çağrılarla ilişkilendirmeniz gereken durumlar için kullanışlıdır.

Örneğin, bağlantılı bir işlev listesi, düşük genel bir eşzamanlı eş zamanlı program, bir görev dağıtıcı veya kesilebilir dosya ayrıştırma uygulamak için kullanılabilir. Örnekler:

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

Tabii ki, bu örnekler kendi başlarına bu kadar faydalı değil. Sadece işlevlerin ne kadar yararlı olabileceğini gösterirler, işlevlerin kendileri çok temel ve esnek değildirler ve bu da onları örneğin güçlendirmenin sağladığı şeyden daha az kullanışlı hale getirir.


2

Bazı GUI düğmelerini gerçek bir C ++ işlevine veya yöntemine bağlamak için gtkmm'de işlevler kullanılır.


Uygulamanızı çok iş parçacıklı hale getirmek için pthread kütüphanesini kullanırsanız, Functors size yardımcı olabilir.
Bir iş parçacığını başlatmak için, argümanlarından biri pthread_create(..)kendi iş parçacığında yürütülecek işlev işaretçisidir.
Ama bir sıkıntı var. Bir olmadıkça bu işaretçi, bir yöntemi işaret olamaz statik yöntemi veya sürece 's sınıfını belirtmek gibi class::method. Ve başka bir şey, yönteminizin arayüzü sadece olabilir:

void* method(void* something)

Böylece (basit bir şekilde), sınıftan yöntemler bir iş parçacığı içinde ekstra bir şey yapmadan çalıştıramazsınız.

C ++ iş parçacıkları ile başa çıkmak için çok iyi bir yol, kendi Threadsınıfını oluşturmaktır . Yöntemleri MyClasssınıftan çalıştırmak istedim , yaptığım şey, bu yöntemleri Functortüretilmiş sınıflara dönüştürmekti .

Ayrıca, Threadsınıfın şu yöntemi vardır: Bu yöntemin static void* startThread(void* arg)
bir işaretçisi çağrı argümanı olarak kullanılır pthread_create(..). Ve startThread(..)arg'da alması gereken void*, herhangi bir Functortüretilmiş sınıfın yığınında, Functor*yürütüldüğünde geri dökülecek ve daha sonra buna run()yöntem olarak adlandırılan bir örneğe dökümlü bir referanstır .


2

Eklemek için, komut desenine varolan eski bir yöntemi sığdırmak için işlev nesneleri kullandım; (OO paradigmasının gerçek OCP'nin güzelliğini hissettiğim tek yer); Ayrıca buraya ilgili işlev adaptör desenini ekler.

Yönteminizin imzası olduğunu varsayalım:

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

Komut deseni için nasıl sığabileceğimizi göreceğiz - bunun için öncelikle bir işlev nesnesi olarak adlandırılabilmesi için bir üye işlev bağdaştırıcısı yazmanız gerekir.

Not - bu çirkin ve Boost bağlama yardımcılarını vb. Kullanabilirsiniz, ancak bunu yapamaz veya istemiyorsanız, bu bir yoldur.

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

Ayrıca, yukarıdaki sınıfın çağrılmasına yardımcı olması için mem_fun3 yardımcı yöntemine ihtiyacımız var.

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

}

Şimdi, parametreleri bağlamak için bir bağlayıcı işlevi yazmalıyız. Yani, işte gidiyor:

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

Ve, binder3 sınıfını kullanmak için bir yardımcı işlev - 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);
}

Şimdi bunu Command sınıfıyla kullanmak zorundayız; aşağıdaki typedef'i kullanın:

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

Buna şöyle diyorsunuz:

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

Not: f3 (); task1-> ThreeParameterTask (21,22,23) yöntemini çağırır;

Aşağıdaki bağlantıda bu desenin tam bağlamı


2

İşlevleri işlev olarak kullanmanın en büyük avantajı, çağrılar arasındaki durumu koruyabilmeleri ve yeniden kullanabilmeleridir. Örneğin, dizeler arasındaki Levenshtein mesafesini hesaplamak için Wagner-Fischer algoritması gibi birçok dinamik programlama algoritması, büyük bir sonuç tablosunu doldurarak çalışır. İşlev her çağrıldığında bu tabloyu ayırmak çok verimsizdir, bu nedenle işlevi bir işlev olarak uygulamak ve tabloyu bir üye değişken yapmak performansı büyük ölçüde artırabilir.

Aşağıda, Wagner-Fischer algoritmasının işlev olarak uygulanmasına bir örnek verilmiştir. Tablonun yapıcıda nasıl tahsis edildiğine ve sonra yeniden operator()boyutlandırılarak gerektiğinde yeniden boyutlandırıldığına dikkat edin.

#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

İşlev, bir işlev içindeki yerel bir işlevi tanımlamayı simüle etmek için de kullanılabilir. Soruya ve diğerine bakın .

Ancak yerel bir işlev dış otomatik değişkenlere erişemez. Lambda (C ++ 11) fonksiyonu daha iyi bir çözümdür.


-10

Functorların çok ilginç bir kullanımını "keşfettim": Bir functor adı olmayan bir yöntem olduğu için bunları bir yöntem için iyi bir isme sahip olmadığımda kullanıyorum ;-)


Neden bir işlevi "adsız yöntem" olarak tanımlıyorsunuz?
Anderson Green

5
Adı olmayan bir işleve lambda denir.
Paul Fultz II
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.