Bir işlev işaretçisi aracılığıyla C ++ sınıfı yöntemlerini çağırma


113

Bir sınıf üyesi işlevi için bir işlev işaretçisini nasıl elde ederim ve daha sonra bu üye işlevi belirli bir nesneyle çağırırım? Yazmak istiyorum:

class Dog : Animal
{
    Dog ();
    void bark ();
}


Dog* pDog = new Dog ();
BarkFunction pBark = &Dog::bark;
(*pBark) (pDog);

Ayrıca, mümkünse, yapıcıyı bir işaretçi aracılığıyla da çağırmak istiyorum:

NewAnimalFunction pNew = &Dog::Dog;
Animal* pAnimal = (*pNew)();    

Bu mümkün mü ve eğer öyleyse, bunu yapmanın tercih edilen yolu nedir?


1
Hala bir nesne üye işlevini çağırmak istiyorsanız 'neden'i gerçekten anlamıyorum, o zaman nesneye bir işaretçi iletin? İnsanlar, sınıfı daha iyi sarmalamanızı sağladığı için şikayet ederse, neden tüm sınıfların miras aldığı bir arayüz sınıfı yapılmasın?
Chad

Pek çok kişi ham üye işaretçi mekaniğini gizlemek için boost :: function kullansa da, komut kalıbı gibi bir şeyin uygulanmasında faydalı olabilir.
CB Bailey

9
Neden o köpeği dinamik olarak ayırıyorsun? Daha sonra nesneyi manuel olarak da silmeniz gerekir. Bu, Java, C # veya başka bir benzer dilden geliyorsunuz ve hala C ++ ile savaşıyorsunuz gibi görünüyor. Düz otomatik bir nesne ( Dog dog;) daha çok istediğiniz şeydir.
sbi

1
@Chad: Çoğunlukla aynı fikirdeyim ama bir referansı geçmenin daha maliyetli olacağı zamanlar var. Bazı if / else hesaplamalarına dayalı bir işlevi çağırabilmekten ziyade, bazı veri türleri (ayrıştırma, hesaplama, vb.) Üzerinde yinelenen bir döngü düşünün. / else bu kontrollerin döngüye girmeden önce yapılıp yapılamayacağını kontrol eder.
Eric

Yanıtlar:


127

Ayrıntılı bilgi için bunu okuyun :

// 1 define a function pointer and initialize to NULL

int (TMyClass::*pt2ConstMember)(float, char, char) const = NULL;

// C++

class TMyClass
{
public:
   int DoIt(float a, char b, char c){ cout << "TMyClass::DoIt"<< endl; return a+b+c;};
   int DoMore(float a, char b, char c) const
         { cout << "TMyClass::DoMore" << endl; return a-b+c; };

   /* more of TMyClass */
};
pt2ConstMember = &TMyClass::DoIt; // note: <pt2Member> may also legally point to &DoMore

// Calling Function using Function Pointer

(*this.*pt2ConstMember)(12, 'a', 'b');

24
Şaşırtıcı bir şekilde bunun *this.*pt2Memberişe yarayacağına karar vermeleri . *daha yüksek önceliğe sahiptir .*... Şahsen ben yine de yazardım this->*pt2Member, bu bir operatör eksik.
Alexis Wilke

7
Neden başlatmanız pt2ConstMembergerekiyor NULL?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

@AlexisWilke neden şaşırtıcı? Doğrudan nesneler için (işaretçiler değil) öyledir (object.*method_pointer), bu nedenle *daha büyük önceliğe sahip olmasını istiyoruz .
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

@ TomášZato, yanılmıyorsam (ve olabilirim), thisbaşvurduğunuz şeyin .*(sub) sınıfının bir örneğine işaretçi olması gerektiğini göstermek için kullanılıyor . Ancak bu benim için yeni sözdizimi, ben sadece burada bağlantılı diğer yanıtlara ve kaynaklara dayanarak tahmin ediyorum. Bunu daha net hale getirmek için bir düzenleme öneriyorum.
c1moore

1
Hay aksi, 100 için tebrikler!
Jonathan Mee

59

Bir sınıf üyesi işlevi için bir işlev işaretçisini nasıl elde ederim ve daha sonra bu üye işlevi belirli bir nesneyle çağırırım?

A ile başlamak en kolayıdır typedef. Bir üye işlevi için, sınıf adını tür bildirimine eklersiniz:

typedef void(Dog::*BarkFunction)(void);

Ardından yöntemi çağırmak için ->*operatörü kullanırsınız :

(pDog->*pBark)();

Ayrıca, mümkünse, kurucuyu bir işaretçi aracılığıyla da çağırmak istiyorum. Bu mümkün mü ve eğer öyleyse, bunu yapmanın tercih edilen yolu nedir?

Bunun gibi kurucularla çalışabileceğine inanmıyorum - körler ve dansçılar özeldir. Bu tür bir şeyi başarmanın normal yolu, temelde sizin için kurucuyu çağıran statik bir işlev olan fabrika yöntemi kullanmak olacaktır. Örnek için aşağıdaki koda bakın.

Temelde tanımladığınız şeyi yapmak için kodunuzu değiştirdim. Aşağıda bazı uyarılar var.

#include <iostream>

class Animal
{
public:

    typedef Animal*(*NewAnimalFunction)(void);

    virtual void makeNoise()
    {
        std::cout << "M00f!" << std::endl;
    }
};

class Dog : public Animal
{
public:

    typedef void(Dog::*BarkFunction)(void);

    typedef Dog*(*NewDogFunction)(void);

    Dog () {}

    static Dog* newDog()
    {
        return new Dog;
    }

    virtual void makeNoise ()
    {
        std::cout << "Woof!" << std::endl;
    }
};

int main(int argc, char* argv[])
{
    // Call member function via method pointer
    Dog* pDog = new Dog ();
    Dog::BarkFunction pBark = &Dog::makeNoise;

    (pDog->*pBark)();

    // Construct instance via factory method
    Dog::NewDogFunction pNew = &Dog::newDog;

    Animal* pAnimal = (*pNew)();

    pAnimal->makeNoise();

    return 0;
}

Normalde bir kullanabilir rağmen Şimdi Dog*bir yerine Animal*polimorfizm büyü sayesinde, bir işlev işaretçisi türü yok değil sınıf hiyerarşisinin arama kuralları izleyin. Dolayısıyla, bir Animal yöntemi işaretçisi, bir Dog yöntemi işaretçisi ile uyumlu değildir, başka bir deyişle Dog* (*)(), bir tür değişkenine a atayamazsınız Animal* (*)().

Statik newDogyöntem, basitçe yeni örnekler oluşturan ve döndüren bir fabrikanın basit bir örneğidir. Statik bir işlev olduğundan, bir typedefdüzenleyiciye sahiptir (sınıf niteleyicisi yoktur).

Yukarıdakileri yanıtladıktan sonra, ihtiyacınız olanı elde etmenin daha iyi bir yolu olup olmadığını merak ediyorum. Bu tür şeyleri yapabileceğiniz birkaç özel senaryo var, ancak probleminiz için daha iyi çalışan başka modeller de bulabilirsiniz. Neyi başarmaya çalıştığınızı daha genel terimlerle açıklarsanız, kovan zihni daha da yararlı olabilir!

Yukarıdakilerle ilgili olarak, Boost bind kitaplığını ve diğer ilgili modülleri çok faydalı bulacağınızdan şüpheniz olmasın .


10
10 yıldan fazla bir süredir C ++ kullanıyorum ve düzenli olarak yeni bir şeyler öğrenmeye devam ediyorum. Daha ->*önce hiç duymamıştım ama şimdi umarım hiç ihtiyacım olmayacak :)
Thomas

31

Burada kimsenin normal işlev işaretçilerinden ziyade " üye işaretçilerine " ihtiyaç duyduğunuzu açıkladığını sanmıyorum .

İşlevlere üye işaretçileri, yalnızca işlev işaretçileri değildir. Uygulama terimlerinde, derleyici basit bir işlev adresi kullanamaz çünkü genel olarak, hangi nesne için başvurunun kaldırılacağını bilene kadar (sanal işlevleri düşünün) aranacak adresi bilemezsiniz. thisElbette örtük parametreyi sağlamak için nesneyi de bilmeniz gerekir .

Onlara ihtiyacınız olduğunu söyledikten sonra, şimdi gerçekten onlardan kaçınmanız gerektiğini söyleyeceğim. Cidden, üye işaretçiler bir acıdır. Aynı hedefe ulaşan nesneye yönelik tasarım modellerine bakmak ya boost::functionda yukarıda belirtildiği gibi bir ya da herhangi bir şeyi kullanmak çok daha mantıklı - yani bu seçimi yapacağınızı varsayarak.

Mevcut koda bu işlev işaretçisini sağlıyorsanız, bu nedenle gerçekten basit bir işlev işaretçisine ihtiyacınız varsa, sınıfın statik bir üyesi olarak bir işlev yazmalısınız. Statik üye işlevi anlamadığından this, nesneyi açık bir parametre olarak içeri aktarmanız gerekir. Bir zamanlar bu satırlarda, işlev işaretçilerine ihtiyaç duyan eski C koduyla çalışmak için alışılmadık bir deyim vardı.

class myclass
{
  public:
    virtual void myrealmethod () = 0;

    static void myfunction (myclass *p);
}

void myclass::myfunction (myclass *p)
{
  p->myrealmethod ();
}

Yana myfunction(kapsam sorunları bir yana) gerçekten sadece normal bir fonksiyonudur, bir işlev işaretçisi normal C şekilde bulunabilir.

DÜZENLE - bu tür bir yönteme "sınıf yöntemi" veya "statik üye işlevi" denir. Üye olmayan bir işlevden temel farkı, ona sınıfın dışından başvurursanız, ::kapsam çözümleme işlecini kullanarak kapsamı belirtmeniz gerektiğidir . Örneğin, işlev işaretçisini elde etmek için ve işlevini &myclass::myfunctionkullanın myclass::myfunction (arg);.

Bu tür şeyler, başlangıçta C ++ yerine C için tasarlanmış olan eski Win32 API'lerini kullanırken oldukça yaygındır. Elbette bu durumda, parametre normalde LPARAM veya işaretçi yerine benzerdir ve biraz döküm gerekir.


Normalde C tarzı bir işlevi kastediyorsanız, 'işlevim' normal bir işlev değildir. "myfunction" daha doğru bir sınıfım metodu olarak adlandırılır. Bir sınıfın metotları, bir C stili fonksiyonun sahip olmadığı bir şeye sahip oldukları için normal fonksiyonlar gibi değildir, ki bu 'bu' göstericidir.
Eric

3
boost kullanmayı tavsiye etmek acımasızdır. Yöntem işaretçileri kullanmanın pratik iyi nedenleri vardır. Alternatif olarak boosttan bahsetmeyi umursamıyorum, ancak birisi başka birinin tüm gerçekleri bilmeden kullanması gerektiğini söylediğinde nefret ediyorum. Arttırmanın bir bedeli vardır! Ve eğer bu gömülü bir platformsa, o zaman mümkün olan bir seçim olmayabilir. Bunun ötesinde, yazmanı gerçekten beğendim.
Eric

@Eric - İkinci olarak, "Boost kullanacaksın" demeyi düşünmedim ve aslında hiç Boost kullanmadım. Amaç (3 yıl sonra bildiğim kadarıyla) insanların alternatifler aramaları ve birkaç olasılık listelemeleri idi. "Ya da her neyse", bir listenin kapsamlı olmadığını gösterir. Üye işaretçilerinin okunabilirlikte bir maliyeti vardır. Kısa kaynak gösterimleri, çalışma zamanı maliyetlerini de gizleyebilir - özellikle bir yönteme ilişkin bir üye işaretçisi hem sanal olmayan hem de sanal yöntemlerle başa çıkmalı ve hangisini bilmelidir.
Steve314

@Eric - Sadece bu değil, aynı zamanda bu sorunlar üye işaretçilerinin taşınabilir olmamasının bir nedenidir - Visual C ++, en azından geçmişte, üye işaretçi türlerinin nasıl temsil edileceği konusunda bazı ekstra ipuçlarına ihtiyaç duyuyordu. Gömülü bir sistem için statik işlev yaklaşımını kullanırdım - bir işaretçinin temsili diğer herhangi bir işlev işaretçisiyle aynıdır, maliyetler açıktır ve taşınabilirlik sorunu yoktur. Ve statik üye işlevi tarafından sarılmış çağrı, çağrının sanal olup olmadığını (derleme zamanında) bilir - sanal yöntemler için olağan vtable aramalarının ötesinde çalışma zamanı kontrollerine gerek yoktur.
Steve314

@Eric - ilk noktaya göre - statik üye işlevinin bir C-stili işlevle tam olarak aynı olmadığının farkındayım (bu nedenle "kapsam sorunları bir yana"), ancak muhtemelen adını eklemeliydim.
Steve314

18
typedef void (Dog::*memfun)();
memfun doSomething = &Dog::bark;
....
(pDog->*doSomething)(); // if pDog is a pointer
// (pDog.*doSomething)(); // if pDog is a reference

2
Olmalıdır: (pDog -> * doSomething) (); // pDog bir işaretçi ise // (pDog. * doSomething) (); // () operatörü daha yüksek önceliğe sahip olduğu için pDog bir referans ise -> * ve. *.
Tomek

12

Minimum çalıştırılabilir örnek

main.cpp

#include <cassert>

class C {
    public:
        int i;
        C(int i) : i(i) {}
        int m(int j) { return this->i + j; }
};

int main() {
    // Get a method pointer.
    int (C::*p)(int) = &C::m;

    // Create a test object.
    C c(1);
    C *cp = &c;

    // Operator .*
    assert((c.*p)(2) == 3);

    // Operator ->*
    assert((cp->*p)(2) == 3);
}

Derleyin ve çalıştırın:

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out

Ubuntu 18.04'te test edilmiştir.

Parantezin sırasını değiştiremez veya atlayamazsınız. Aşağıdakiler çalışmıyor:

c.*p(2)
c.*(p)(2)

GCC 9.2 şunlarla başarısız olur:

main.cpp: In function int main()’:
main.cpp:19:18: error: must use ‘.*’ or ‘->*’ to call pointer-to-member function in p (...)’, e.g. ‘(... ->* p) (...)’
   19 |     assert(c.*p(2) == 3);
      |

C ++ 11 standardı

.*ve ->*bir olan bir operatör , bu amaç için, C ++ tanıtıldı ve C mevcut değildir

C ++ 11 N3337 standart taslağı :

  • 2.13 "Operatörler ve noktalayıcılar" içerdiği tüm operatörler, bir listesi vardır .*ve ->*.
  • 5.5 "İşaretçiden üyeye operatörler" ne yaptıklarını açıklar

11

Buraya bir yöntemden bir işlev işaretçisinin (yöntem işaretçisi değil) nasıl oluşturulacağını öğrenmek için geldim, ancak buradaki yanıtların hiçbiri bir çözüm sağlamıyor. İşte bulduğum şey:

template <class T> struct MethodHelper;
template <class C, class Ret, class... Args> struct MethodHelper<Ret (C::*)(Args...)> {
    using T = Ret (C::*)(Args...);
    template <T m> static Ret call(C* object, Args... args) {
        return (object->*m)(args...);
    }
};

#define METHOD_FP(m) MethodHelper<decltype(m)>::call<m>

Yani örneğiniz için şimdi yapacaksınız:

Dog dog;
using BarkFunction = void (*)(Dog*);
BarkFunction bark = METHOD_FP(&Dog::bark);
(*bark)(&dog); // or simply bark(&dog)

Düzenleme:
C ++ 17 kullanarak daha da iyi bir çözüm var:

template <auto m> struct MethodHelper;
template <class C, class Ret, class... Args, Ret (C::*m)(Args...)> struct MethodHelper<m> {
    static Ret call(C* object, Args... args) {
        return (object->*m)(args...);
    }
};

makro olmadan doğrudan kullanılabilir:

Dog dog;
using BarkFunction = void (*)(Dog*);
BarkFunction bark = MethodHelper<&Dog::bark>::call;
(*bark)(&dog); // or simply bark(&dog)

constSizin gibi değiştiricilere sahip yöntemler için, aşağıdakiler gibi bazı daha fazla uzmanlığa ihtiyaç duyabilirsiniz:

template <class C, class Ret, class... Args, Ret (C::*m)(Args...) const> struct MethodHelper<m> {
    static Ret call(const C* object, Args... args) {
        return (object->*m)(args...);
    }
};

6

Üye işlevleri çağırmak için işlev işaretçileri kullanamamanızın nedeni, sıradan işlev işaretçilerinin genellikle işlevin yalnızca bellek adresi olmasıdır.

Bir üye işlevini çağırmak için iki şeyi bilmeniz gerekir:

  • Hangi üye işlevi aranacak
  • Hangi örnek kullanılmalı (üye işlevi)

Sıradan işlev işaretçileri her ikisini birden depolayamaz. C ++ üye işlev işaretçileri a) 'yı depolamak için kullanılır, bu nedenle bir üye işlev işaretçisini çağırırken örneği açıkça belirtmeniz gerekir.


1
Buna oy verdim, ancak OP'nin "hangi örnekle" neyi kastettiğinizi bilmemesi durumunda bir açıklama noktası ekleyeceğim. İçsel 'bu' işaretçisini açıklamak için genişlerdim.
Eric

6

Bir sınıf üyesine yönelik bir işlev işaretçisi, boost :: işlevini kullanmak için gerçekten uygun bir sorundur. Küçük örnek:

#include <boost/function.hpp>
#include <iostream>

class Dog 
{
public:
   Dog (int i) : tmp(i) {}
   void bark ()
   {
      std::cout << "woof: " << tmp << std::endl;
   }
private:
   int tmp;
};



int main()
{
   Dog* pDog1 = new Dog (1);
   Dog* pDog2 = new Dog (2);

   //BarkFunction pBark = &Dog::bark;
   boost::function<void (Dog*)> f1 = &Dog::bark;

   f1(pDog1);
   f1(pDog2);
}

2

Yeni bir nesne oluşturmak için, yukarıda belirtildiği gibi yeni yerleşimi kullanabilir veya sınıfınızın nesnenin bir kopyasını oluşturan bir clone () yöntemini uygulamasını sağlayabilirsiniz. Daha sonra, yukarıda açıklandığı gibi, nesnenin yeni örneklerini oluşturmak için bir üye işlev işaretçisi kullanarak bu klon yöntemini çağırabilirsiniz. Klonlamanın avantajı, bazen nesnenin türünü bilmediğiniz bir temel sınıfa yönelik bir işaretçi ile çalışıyor olmanızdır. Bu durumda, clone () yönteminin kullanımı daha kolay olabilir. Ayrıca, clone (), istediğiniz buysa, nesnenin durumunu kopyalamanıza izin verir.


klonlar pahalı olabilir ve performans bir sorun veya endişe kaynağıysa, OP bunlardan kaçınmak isteyebilir.
Eric

0

Bunu std :: function ve std :: bind .. ile yaptım.

Bu EventManager sınıfını, olay türlerini (bunlar sadece const unsigned int, büyük bir ad alanı kapsamlı enumum var) bu olay türü için bir işleyici vektörüne eşleyen bir unordered_map içinde depolayan bu EventManager sınıfını yazdım.

EventManagerTests sınıfımda, şunun gibi bir olay işleyicisi ayarlıyorum:

auto delegate = std::bind(&EventManagerTests::OnKeyDown, this, std::placeholders::_1);
event_manager.AddEventListener(kEventKeyDown, delegate);

AddEventListener işlevi şöyledir:

std::vector<EventHandler>::iterator EventManager::AddEventListener(EventType _event_type, EventHandler _handler)
{
    if (listeners_.count(_event_type) == 0) 
    {
        listeners_.emplace(_event_type, new std::vector<EventHandler>());
    }
    std::vector<EventHandler>::iterator it = listeners_[_event_type]->end();
    listeners_[_event_type]->push_back(_handler);       
    return it;
}

EventHandler türü tanımı şöyledir:

typedef std::function<void(Event *)> EventHandler;

Sonra EventManagerTests :: RaiseEvent'e geri dönün, şunu yapıyorum:

Engine::KeyDownEvent event(39);
event_manager.RaiseEvent(1, (Engine::Event*) & event);

EventManager :: RaiseEvent kodu şöyledir:

void EventManager::RaiseEvent(EventType _event_type, Event * _event)
{
    if (listeners_.count(_event_type) > 0)
    {
        std::vector<EventHandler> * vec = listeners_[_event_type];
        std::for_each(
            begin(*vec), 
            end(*vec), 
            [_event](EventHandler handler) mutable 
            {
                (handler)(_event);
            }
        );
    }
}

Bu çalışıyor. Çağrıyı EventManagerTests :: OnKeyDown'da alıyorum. Temizleme zamanı gelen vektörleri silmem gerekiyor, ancak bunu yaptıktan sonra herhangi bir sızıntı yok. Bir olayı oluşturmak, bilgisayarımda yaklaşık 5 mikrosaniye sürüyor, bu yaklaşık 2008. Tam olarak süper hızlı değil, ama. Bunu bildiğim sürece yeterince adil ve onu ultra sıcak kodda kullanmıyorum.

Kendi std :: fonksiyonumu ve std :: bind'imi yuvarlayarak ve belki de sırasız bir vektör haritası yerine bir dizi dizisi kullanarak bunu hızlandırmak istiyorum, ancak bir üye fonksiyonunu nasıl saklayacağımı tam olarak çözemedim pointer ve çağrılan sınıf hakkında hiçbir şey bilmeyen koddan çağırın. Kirpik'in cevabı Çok İlginç görünüyor ..

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.