Ücretsiz bir işlevin beklendiği bir üye işlevini nasıl geçirebilirim?


122

Soru şudur: bu kod parçasını düşünün:

#include <iostream>


class aClass
{
public:
    void aTest(int a, int b)
    {
        printf("%d + %d = %d", a, b, a + b);
    }
};

void function1(void (*function)(int, int))
{
    function(1, 1);
}

void test(int a,int b)
{
    printf("%d - %d = %d", a , b , a - b);
}

int main (int argc, const char* argv[])
{
    aClass a();

    function1(&test);
    function1(&aClass::aTest); // <-- How should I point to a's aClass::test function?

    return 0;
}

a'S' i aClass::testbir argüman olarak nasıl kullanabilirim function1? Bunu yapmakta sıkışıp kaldım.

Sınıfın bir üyesine erişmek istiyorum.



16
Bu kesinlikle bir kopya değildir (en azından bağlantılı olan belirli sorunun kopyası değil). Bu soru, bir işleve işaret eden bir üyenin nasıl bildirileceğiyle ilgilidir; bu, statik olmayan bir üye işleve bir parametre olarak bir göstericinin nasıl iletileceğiyle ilgilidir.
CarLuva

Yanıtlar:


144

İşlev işaretlerini kullanmanın yanlış bir tarafı yoktur. Bununla birlikte, statik olmayan üye işlevlere işaretçiler normal işlev işaretçilerine benzemez: üye işlevlerin, işleve örtük bir argüman olarak iletilen bir nesnede çağrılması gerekir. Yukarıdaki üye işlevinizin imzası, bu nedenle

void (aClass::*)(int, int)

kullanmaya çalıştığınız tip yerine

void (*)(int, int)

Bir yaklaşım, üye işlevini yapmaktan oluşabilir, staticbu durumda herhangi bir nesnenin çağrılmasını gerektirmez ve türle birlikte kullanabilirsiniz void (*)(int, int).

Eğer sınıfın herhangi statik olmayan üyesine erişmek gerekiyorsa ve siz işlevi C arayüzünün bir parçası olduğu için, örneğin işlev işaretçileri ile sopa gerekir, en iyi seçenek her zaman geçmektir void*işlev işaretçileri ve çağrı alarak fonksiyonunuza üyeniz, öğesinden bir nesne alan void*ve ardından üye işlevini çağıran bir yönlendirme işlevi aracılığıyla .

Uygun bir C ++ arabiriminde, işlev nesnelerinin rastgele sınıf türlerini kullanması için işlevinizin şablonlu argüman almasını sağlamaya bir göz atmak isteyebilirsiniz. Şablonlu bir arayüz kullanmak istenmiyorsa, aşağıdaki gibi bir şey kullanmalısınız std::function<void(int, int)>: bunlar için uygun şekilde çağrılabilir bir fonksiyon nesnesi oluşturabilirsiniz, örneğin, kullanarak std::bind().

Sınıf türü veya uygun için bir şablon bağımsız değişkeni kullanan tür güvenli yaklaşımlar , yanlış türe dönüştürme nedeniyle hata olasılığını ortadan kaldırdıklarından std::function<...>bir void*arabirim kullanmaktan daha tercih edilir .

Bir üye işlevi çağırmak için bir işlev işaretçisinin nasıl kullanılacağını açıklığa kavuşturmak için, işte bir örnek:

// the function using the function pointers:
void somefunction(void (*fptr)(void*, int, int), void* context) {
    fptr(context, 17, 42);
}

void non_member(void*, int i0, int i1) {
    std::cout << "I don't need any context! i0=" << i0 << " i1=" << i1 << "\n";
}

struct foo {
    void member(int i0, int i1) {
        std::cout << "member function: this=" << this << " i0=" << i0 << " i1=" << i1 << "\n";
    }
};

void forwarder(void* context, int i0, int i1) {
    static_cast<foo*>(context)->member(i0, i1);
}

int main() {
    somefunction(&non_member, 0);
    foo object;
    somefunction(&forwarder, &object);
}

Tamam, bu cevabı beğendim! "Boşluktan * bir nesne alan ve ardından üye işlevini çağıran bir yönlendirme işlevi aracılığıyla üyenizi çağırın" ile ne demek istediğinizi belirtebilir misiniz, yoksa ona faydalı bir bağlantı paylaşabilir misiniz? Teşekkürler
Jorge Leitao

Sanırım anladım. (Gönderinizi düzenledim) Açıklama ve örnek için teşekkürler, gerçekten yardımcı oldum. Sadece onaylamak için: işaret etmek istediğim her üye işlevi için bir iletici yapmam gerekiyor. Sağ?
Jorge Leitao

Şey, evet, bir nevi. Şablonları ne kadar etkili kullandığınıza bağlı olarak, farklı sınıflar ve üye işlevleriyle çalışabilen yönlendirme şablonları oluşturmaktan kurtulabilirsiniz. Bunun nasıl yapılacağı ayrı bir işlev olurdu sanırım ;-)
Dietmar Kühl

1
Bu yanıtı void*, kullandığı için beğenmedim , bu da çok kötü hatalar alabileceğiniz anlamına geliyor, çünkü artık yazılmamış.
Superlokkus

@Superlokkus: Bir alternatifle bizi aydınlatabilir misiniz?
Dietmar Kühl

88

@Pete Becker'in cevabı gayet iyi, ancak classörneği function1C ++ 11'e açık bir parametre olarak iletmeden de yapabilirsiniz :

#include <functional>
using namespace std::placeholders;

void function1(std::function<void(int, int)> fun)
{
    fun(1, 1);
}

int main (int argc, const char * argv[])
{
   ...

   aClass a;
   auto fp = std::bind(&aClass::test, a, _1, _2);
   function1(fp);

   return 0;
}

1
void function1(std::function<void(int, int)>)doğru?
Deqing

2
Fonksiyon argümanına bir değişken adı vermeniz gerekir ve ardından değişken adı gerçekte ilettiğiniz şeydir. Yani: void function1(std::function<void(int, int)> functionToCall)ve sonra functionToCall(1,1);. Cevabı düzenlemeye çalıştım ama birisi nedense mantıklı gelmediği için cevabı reddetti. Bir noktada olumlu oy verilip verilmediğini göreceğiz.
salak Mühendisi

1
@DorkyEngineer Bu oldukça tuhaf, haklı olmalısın ama bu hatanın bu kadar uzun süre nasıl fark edilmediğini bilmiyorum. Her neyse, cevabı şimdi düzenledim.
Matt Phillips

2
Bunu buldum yazıyı std :: işlevinden ciddi performans cezası olduğunu söyleyerek.
kevin

2
@kevin, bu gönderinin cevapları karşılaştırmada bir kusur gösterdiğinden yorumunuzu gözden geçirmek isteyebilirsiniz.
Jorge Leitao

54

Üye işlevine bir işaretçi, bir göstericiden işleve göre farklıdır. Bir işaretçi aracılığıyla bir üye işlevi kullanmak için, ona bir işaretçiye (tabii ki) ve onu uygulayacak bir nesneye ihtiyacınız vardır. Uygun versiyonu Yani function1olurdu

void function1(void (aClass::*function)(int, int), aClass& a) {
    (a.*function)(1, 1);
}

ve onu adlandırmak için:

aClass a; // note: no parentheses; with parentheses it's a function declaration
function1(&aClass::test, a);

1
Çok teşekkür ederim. Burada parantezlerin sayıldığını öğrendim: function1 (& (aClass :: test), a) MSVC2013 ile çalışıyor ama gcc ile çalışmıyor. gcc'nin & karakterine doğrudan sınıf adının önünde ihtiyacı var (kafa karıştırıcı buluyorum, çünkü & operatörü sınıfın değil fonksiyonun adresini alıyor)
kritzel_sw

Ben functionin void (aClass::*function)(int, int)bir tür olduğunu düşündüm , çünkü bir tür girdi typedef void (aClass::*function)(int, int).
Olumide

@Olumide - typedef int X;bir türü tanımlar; int X;bir nesne yaratır.
Pete Becker

11

2011'den beri, değiştirebiliyorsanız function1, şu şekilde yapın:

#include <functional>
#include <cstdio>

using namespace std;

class aClass
{
public:
    void aTest(int a, int b)
    {
        printf("%d + %d = %d", a, b, a + b);
    }
};

template <typename Callable>
void function1(Callable f)
{
    f(1, 1);
}

void test(int a,int b)
{
    printf("%d - %d = %d", a , b , a - b);
}

int main()
{
    aClass obj;

    // Free function
    function1(&test);

    // Bound member function
    using namespace std::placeholders;
    function1(std::bind(&aClass::aTest, obj, _1, _2));

    // Lambda
    function1([&](int a, int b) {
        obj.aTest(a, b);
    });
}

( canlı demo )

Ayrıca, bozuk nesne tanımınızı düzelttiğime de dikkat edin ( aClass a();bir işlev bildirir).


2

Benzer bir soru sordum ( C ++ openframeworks diğer sınıflardan geçersiz geçiyor ) ancak bulduğum cevap daha netti, bu yüzden burada gelecekteki kayıtlar için açıklama:

std :: function'ı şu şekilde kullanmak daha kolaydır:

 void draw(int grid, std::function<void()> element)

ve sonra şu şekilde arayın:

 grid.draw(12, std::bind(&BarrettaClass::draw, a, std::placeholders::_1));

hatta daha kolay:

  grid.draw(12, [&]{a.draw()});

referansla yakalayan nesneyi çağıran bir lambda oluşturduğunuz yer


1
Bunun CPP olarak etiketlendiği göz önüne alındığında, bunun en temiz çözüm olduğunu düşünüyorum. Her aramada kopyalamak yerine std::functionin için sabit bir referans kullanabiliriz draw.
Sohaib

2
İşlev herhangi bir parametre almadığından, bu örnekte yer tutucu kullanılmamalıdır.
kroiz

1

Üyeyi statik olarak çalıştırdım ve tüm çalışıyor:

#include <iostream>

class aClass
{
public:
    static void aTest(int a, int b)
    {
        printf("%d + %d = %d\n", a, b, a + b);
    }
};

void function1(int a,int b,void function(int, int))
{
    function(a, b);
}

void test(int a,int b)
{
    printf("%d - %d = %d\n", a , b , a - b);
}

int main (int argc, const char* argv[])
{
    aClass a;

    function1(10,12,test);
    function1(10,12,a.aTest); // <-- How should I point to a's aClass::test function?

    getchar();return 0;
}

"A'nın aClass :: testini function1'e bir argüman olarak nasıl kullanabilirim?" Ana sorusunu çözmekle kalmaz. ancak sınıfın özel değişkenlerini sınıfa harici bir kod kullanarak değiştirmekten kaçınmanız önerilir
mathengineer

1

Bu inanılmaz derecede basit çözümün neden kabul edildiğinden emin değilim:

#include <stdio.h>

class aClass
{
public:
    void aTest(int a, int b)
    {
        printf("%d + %d = %d\n", a, b, a + b);
    }
};

template<class C>
void function1(void (C::*function)(int, int), C& c)
{
    (c.*function)(1, 1);
}
void function1(void (*function)(int, int)) {
  function(1, 1);
}

void test(int a,int b)
{
    printf("%d - %d = %d\n", a , b , a - b);
}

int main (int argc, const char* argv[])
{
    aClass a;

    function1(&test);
    function1<aClass>(&aClass::aTest, a);
    return 0;
}

Çıktı:

1 - 1 = 0
1 + 1 = 2

0

a Örneği gerçekten kullanmanız gerekmiyorsa (yani onu @mathengineer'ın cevabı gibi statik hale getirebilirsiniz), yakalanmayan bir lambda'yı kolayca iletebilirsiniz. (işaretçi işlevine çürüyen)


#include <iostream>

class aClass
{
public:
   void aTest(int a, int b)
   {
      printf("%d + %d = %d", a, b, a + b);
   }
};

void function1(void (*function)(int, int))
{
    function(1, 1);
}

int main()
{
   //note: you don't need the `+`
   function1(+[](int a,int b){return aClass{}.aTest(a,b);}); 
}

Wandbox


not: aClassyapımı maliyetliyse veya yan etkisi varsa, bu iyi bir yol olmayabilir.


-1

Artık kafanızı düzmeyi bırakabilirsiniz. Burada, düz C işlevlerini bağımsız değişken olarak alan mevcut işlevleri desteklemek için üye işlevinin sarmalayıcısı verilmiştir . yönerge burada anahtardır.thread_local

http://cpp.sh/9jhk3

// Example program
#include <iostream>
#include <string>

using namespace std;

typedef int FooCooker_ (int);

// Existing function
extern "C" void cook_10_foo (FooCooker_ FooCooker) {
    cout << "Cooking 10 Foo ..." << endl;
    cout << "FooCooker:" << endl;
    FooCooker (10);
}

struct Bar_ {
    Bar_ (int Foo = 0) : Foo (Foo) {};
    int cook (int Foo) {
        cout << "This Bar got " << this->Foo << endl;
        if (this->Foo >= Foo) {
            this->Foo -= Foo;
            cout << Foo << " cooked" << endl;
            return Foo;
        } else {
            cout << "Can't cook " <<  Foo << endl;
            return 0;
        }
    }
    int Foo = 0;
};

// Each Bar_ object and a member function need to define
// their own wrapper with a global thread_local object ptr
// to be called as a plain C function.
thread_local static Bar_* BarPtr = NULL;
static int cook_in_Bar (int Foo) {
    return BarPtr->cook (Foo);
}

thread_local static Bar_* Bar2Ptr = NULL;
static int cook_in_Bar2 (int Foo) {
    return Bar2Ptr->cook (Foo);
}

int main () {
  BarPtr = new Bar_ (20);
  cook_10_foo (cook_in_Bar);

  Bar2Ptr = new Bar_ (40);
  cook_10_foo (cook_in_Bar2);

  delete BarPtr;
  delete Bar2Ptr;
  return 0;
}

Lütfen bu yaklaşımla ilgili herhangi bir sorun hakkında yorum yapın.

Diğer yanıtlar mevcut düz Cişlevleri çağırmada başarısız : http://cpp.sh/8exun


3
Bu nedenle, örneği sarmak için std :: bind veya bir lambda kullanmak yerine bir global değişkene güvenirsiniz. Diğer cevaplara göre bu yaklaşımın herhangi bir avantajını göremiyorum.
Süper

@super, Diğer yanıtlar, düz Cişlevleri bağımsız değişken olarak alan mevcut işlevleri çağıramaz .
Necktwi

2
Soru, üye işlevinin nasıl çağrılacağıdır. Ücretsiz bir işlevi geçmek, söz konusu OP için zaten çalışıyor. Siz de hiçbir şey aktarmıyorsunuz. Burada yalnızca sabit kodlanmış işlevler ve genel bir Foo_ işaretçisi vardır. Farklı bir üye işlevini çağırmak isterseniz bu nasıl ölçeklenir? Altta yatan işlevleri yeniden yazmanız veya her hedef için farklı işlevler kullanmanız gerekir.
süper
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.