🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: de.comp.lang.c++
Article #1697

Re: Frage zu C++-Funktionsadresse

#1697
From: Dietmar Kuehl
Date: Tue, 27 Feb 1996 17:37
179 lines
5669 bytes
Hi,

Detlef Volk (detlef.volk@shl.tu-ilmenau.de) wrote:
: Ich moechte von der Funktion einer Klasse die Adresse in
: eine Tabelle ablegen. Ueber den Adressoperator & ist dies nicht moeglich,
: da ich die Funktion nicht 'static' machen moechte.

Die Adresse, die Du haben moechtest, ist ein 'Pointer-to-Member' und
ist als solcher nicht genau dasselbe wie ein "normaler"
Funktionspointer.

: Beispiel:
: im Header:
: 	class TBsp {
: 		void Fkt(void);
: 		void BspFkt(void);
: 	};

: im CPP-File:
: 	typedef void (* VOIDFKT)(void);
: 	...
: 	void TBsp::Fkt(void) {
: 		VOIDFKT		Beispiele[20];
: 		...
: 		Beispiele[0]= &TBsp::BspFkt;	<- Compilerfehler !
: 		...
: 	}

'&TBsp::BspFkt' hat nicht den Typ 'VOIDFKT', sondern den Typ 'void
(TBsp::*)(void)'.  Es gibt also einen Typkonflikt. Man kann die
Memberfunktionen mittels der Pointer-to-Member auch nicht ganz normal
aufrufen, sondern muss sie mit dem '.*' oder dem '->*' Operator
aufrufen, z.B.:

  void f(TBsp &obj, void (TBsp::*fkt)(void))
  {
    (obj.*fkt)()
  }

Ananlog wird '->*' verwendet, wenn man einen Pointer anstatt einem
Objekt oder einer Referenz hat. Der Grund fuer diese Konstruktion ist,
dass die Memberfunktionen einen impliziten 'this'-Pointer haben. Das
Objekt, auf dass 'this' zeigt, muss irgendwo angegeben werden.

: Das Problem besteht also darin, dass vom Compiler 
: beim Adressoperator & die Adresse direkt in den Maschinencode ein-
: geschrieben wird, aber die kann der Compiler nicht erzeugen, da sie erst 
: zur Laufzeit feststeht.

Das trifft es nicht ganz. Die Kombination von Pointer-to-Member (liegt
zur Compiler- bzw. Linkzeit fest) und Objekt (wird erst zur Laufzeit
bestimmt) ist erst zur Laufzeit vorhanden. Um das zu bestimmen, kann
man sich mit einer Klasse helfen, die einen Pointer-to-Member und ein
Objekt (oder einen Pointer auf ein solches) enthaelt. Oft sollen dann
noch Funktionen von verschiedenen Klassen aufgerufen werden (etwa wenn
ein Callback-Mechanismus implementiert wird). Um das auch noch in den
Griff zu bekommen, braucht man eine virtuelle Funktion, die den
entsprechenden Pointer-to-Member aufruft. Hier ist ein Beispiel, wie
man sowas implementieren koennte (leider ist das Beispiel etwas
laenglich; dafuer ist es komplett und laesst sich z.B. mit gcc-2.7.2
compilieren):

  #include <iostream.h>
  #include <cassert>

  class CallbackRC; // this declaration is not really necessary...

  class CallbackBase
  {
    friend class CallbackRC;
  private:
    int i_refCount; // Used by 'CallbackRC'

  public:
    CallbackBase():                     i_refCount(0) {}
    CallbackBase(CallbackBase const &): i_refCount(0) {}
    ~CallbackBase() { assert(i_refCount == 0); }
    CallbackBase &operator= (CallbackBase const &) { return *this; }
    virtual void invoke() = 0;
  };

  class CallbackRC
  {
  private:
    CallbackBase *i_ptr;

  public:
    CallbackRC(CallbackBase *ptr = 0):
      i_ptr(ptr)
    {
      assert((i_ptr == 0) || (i_ptr->i_refCount == 0));
      if (i_ptr != 0)
	i_ptr->i_refCount = 1;
    }
    CallbackRC(CallbackRC const &rc):
      i_ptr(rc.i_ptr)
    {
      assert((i_ptr == 0) || (i_ptr->i_refCount > 0));
      if (i_ptr != 0)
	++(i_ptr->i_refCount);
    }
    ~CallbackRC()
    {
      if (i_ptr != 0 && --(i_ptr->i_refCount) == 0)
	delete i_ptr;
    }
    CallbackRC &operator= (CallbackRC const &rc)
    {
      if (this != &rc)
      {
	if (i_ptr != 0 && --(i_ptr->i_refCount) == 0)
	  delete i_ptr;

	i_ptr = rc.i_ptr;

	assert((i_ptr == 0) || (i_ptr->i_refCount > 0));
	if (i_ptr != 0)
	  ++(i_ptr->i_refCount);
      }
      return *this;
    }

    CallbackBase &operator* () const  { return *i_ptr; }
    CallbackBase *operator-> () const { return i_ptr; }
  };

  template <class T>
  class Callback: public CallbackBase
  {
  private:
    T    i_obj;
    void (T::*i_fkt)(void);
  public:
    Callback(void (T::*fkt)(void), T const &init = T()):
      i_obj(init),
      i_fkt(fkt)
    {
    }
    void invoke() { (i_obj.*i_fkt)(); }
  };

  class Bsp1
  {
  public:
    void fkt1(void) { cout << "Bsp1::fkt1()\n"; }
    void fkt2(void) { cout << "Bsp1::fkt2()\n"; }
  };

  class Bsp2
  {
  public:
    void fkt1(void) { cout << "Bsp2::fkt1()\n"; }
    void fkt2(void) { cout << "Bsp2::fkt2()\n"; }
  };

  int main()
  {
    CallbackRC list[] =
    {
      CallbackRC(new Callback<Bsp1>(&Bsp1::fkt1)),
      CallbackRC(new Callback<Bsp1>(&Bsp1::fkt2)),
      CallbackRC(new Callback<Bsp2>(&Bsp2::fkt1)),
      CallbackRC(new Callback<Bsp2>(&Bsp2::fkt2))
    };

    for (int i = 0; i < sizeof(list) / sizeof(CallbackRC); ++i)
      list[i]->invoke();

    return 0;
  }

Zusaetzlich zu dem, was ich in diesem Artikel beschrieben habe, gibt es
noch Information in dem C++ FAQ in Section 18, etwa unter folgender URL:
<http://www.informatik.uni-konstanz.de/~kuehl/cpp/cppfaq.htm#Q111> (und
folgende FAQs).
--
dietmar.kuehl@uni-konstanz.de
http://www.informatik.uni-konstanz.de/~kuehl
I am a realistic optimist - that's why I appear to be slightly pessimistic
Technical Details
Message-ID: <4gvfg4$39g@news.BelWue.DE>
Path: arc1-ca.pugleaf.net!news.belwue.de!horn!kuehl
References: <4gsia4$90m@link.rz.tu-ilmenau.de>

Show Full Headers
Complete Article Headers
Path: arc1-ca.pugleaf.net!news.belwue.de!horn!kuehl
From: kuehl@horn.informatik.uni-konstanz.de (Dietmar Kuehl)
Newsgroups: de.comp.lang.c++
Subject: Re: Frage zu C++-Funktionsadresse
Date: 27 Feb 1996 17:37:08 GMT
Organization: Fakultät für Mathematik und Informatik
Message-ID: <4gvfg4$39g@news.BelWue.DE>
References: <4gsia4$90m@link.rz.tu-ilmenau.de>
Reply-To: dietmar.kuehl@uni-konstanz.de
NNTP-Posting-Host: horn.informatik.uni-konstanz.de
X-Newsreader: TIN [version 1.2 PL2]