In einem vorherigen Post wurden callbacks mit Hilfe von verzögerten Funktionionsaufrufen vorgestellt. Reallisiert wurden diese mit Funktionsobjekten (boost::function), die mit boost::bind gebaut wurden.

Mit Hilfe von std::for_each und boost::bind kann man Methoden von nicht nur einer, sondern von mehreren Instanzen einer Klasse in STL-Collections aufrufen. Der Algorithmus std::for_each erwartet als drittes Argument eine Referenz auf einen Funktor.  In der Iteration über alle Elemente der Collection übergibt er dem Funktor eine Referenz auf das aktuelle Element. boost::bind baut uns diesen Funktor. Dieser Funktor wiederrum ruft dann für jede Instanz in der Collection die übergebene Methode mit den übergebenen Parametern in der angegebenen Reihenfolge auf. Die Instanzen können in der Collection abgelegt sein als Kopie, Pointer oder Shared Pointer.

Beispielklasse A

Betrachten wir dazu eine Beispielklasse A mit 3 Methoden, die je eine unterschiedliche Signatur haben:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
namespace classes
{
  class A
  {
  public:
    void f_1()
    {
      boost::format f(
        "%1% called. instance-addr: 0x%2%");
      f % __FUNCTION__ % this;
      std::cout << f.str() << std::endl;
    }
    int  f_2(double d, const std::string& s)
    {
      const int result = 1;
      boost::format f(
        "%1% called. instance-addr: 0x%2%. d: %3%, s: %4%. return: %5%");
      f % __FUNCTION__ % this % d % s % result;
      std::cout << f.str() << std::endl;
      return result;
    }
    void f_3(int a1, int a2, int a3)
    {
      boost::format f(
        "%1% called. instance-addr: 0x%2%. a1: %3%, a2: %4%, a3: %5%.");
      f % __FUNCTION__ % this % a1 % a2 % a3;
      std::cout << f.str() << std::endl;
    }
  };
}

Für Kopien

Hier ein Beispiel für Kopien von Instanzen in Containern. Aufgerufen wird die Methode A::f_1:

31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// Instances of classes::A for common use in case of objects and raw ptrs to
// objects:
classes::A a, b, c;
 
// Put object-copies in vector. Use boost::bind to build functor
// std::for_each can use. Provide functor with the required args in the
// right order for being able to call the indicated member function of the
// objects properly:
 
// Create vector. Create instances of A. Put copies of em in vector:
std::vector<classes::A> vec_copies;
vec_copies.push_back(a);
vec_copies.push_back(b);
vec_copies.push_back(c);
 
// Call f_1(): No return, no params
std::for_each(
  vec_copies.begin(),
  vec_copies.end(),
  boost::bind( // Builds functor of type '<void (classes::A&)>' ...
    &classes::A::f_1, // ... for call of type '<void (void)>'
    _1 // place holder for instance. Is each element of vec.
));

Für Pointer

Das ganze geht auch mit gleicher Syntax dank boost::bind für Pointer auf Instanzen.
Hier ein Beispiel für einen Methodenaufruf (A::f_2) mit Argumenten. Ein Rückgabewert der Methode wird von std::for_each nicht ausgewertet:

54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
std::vector<classes::A*> vec_ptrs;
vec_ptrs.push_back(&a);
vec_ptrs.push_back(&b);
vec_ptrs.push_back(&c);
 
// Call f_2():
std::for_each(
  vec_ptrs.begin(),
  vec_ptrs.end(),
  boost::bind(
	&classes::A::f_2,
	_1, // place holder for instance. If used for member-functions,
	    // always the first param is the instance-address. Is each
	    // element of vec.
	0.42,			     // 1. (bound) arg
	"call of obj-ptrs with bind" // 2. (bound) arg
  ));

Für shared_ptr

Das ganze geht auch mit gleicher Syntax dank boost::bind für shared_ptr auf Instanzen.
Hier ein Beispiel für einen Methodenaufruf (A::f_2) mit Argumenten. Ein Rückgabewert der
Methode wird von std::for_each nicht ausgewertet:

71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
std::vector<boost::shared_ptr<classes::A> > vec_shps;
vec_shps.push_back(boost::shared_ptr<classes::A>(new classes::A()));
vec_shps.push_back(boost::shared_ptr<classes::A>(new classes::A()));
vec_shps.push_back(boost::shared_ptr<classes::A>(new classes::A()));
 
// Call f_2():
std::for_each(
  vec_shps.begin(),
  vec_shps.end(),
  boost::bind(
  &classes::A::f_2,
  _1, // place holder for instance. If used for member-functions,
      // always the first param is the instance-address. Is each
      // element of vec.
  0.666,            // 1. (bound) arg
  "with shared_ptr!" // 2. (bound) arg
  ));

Direkter Aufruf mit BOOST_FOREACH

Etwas unsportlicher, weil leichter zu lesen ist der folgende direkte Aufruf
einer Methode (A::f_3) mehrerer Instanzen in einer STL-Collection mit BOOST_FOREACH:

88
89
90
91
92
boost::shared_ptr<classes::A> shp_a;
BOOST_FOREACH(shp_a,vec_shps)
{
  shp_a->f_3(1,2,3);
}

Selbstverständlich funktioniert dieser Aufruf ganz boost-like auch wieder mit Kopien und Raw-Pointern.

Für eine Map ist der Aufruf etwas anders, da hier Elemente vom Typ std::pair
gespeichert werden:

93
94
95
96
97
98
99
100
101
102
103
// direct walk with BOOST_FOR_EACH: map, shared_ptr to fn with 3 int args:
std::map<std::string,boost::shared_ptr<classes::A> > map_shps;
map_shps["0"] = vec_shps[0]; // see line 71 and following ...
map_shps["1"] = vec_shps[1];
map_shps["2"] = vec_shps[2];
 
std::pair<std::string,boost::shared_ptr<classes::A> > element;
BOOST_FOREACH(element,map_shps)
{
  element.second->f_3(1,2,3);
}

Ein vc++2008-projekt mit dem Beispielprogramm kann hier downgeloded werden.
Boost-Version: 1.42 aufwärts. Proj.C++.Additional Include Directories:
Pfad zu boost angeben, Proj.Linker.General.Additional Library Directories:
Pfad zu boost\stage\lib angeben.

play_with_boost_function_vc++2008_proj

Bis zum nächsten Post,

Daniel

Pandorum   November 14th, 2010

Gestern Pandorum angeschaut. Wow, klasse SF-Film. Positiv: Man weis am Anfang noch nicht alles, und Stück für Stück setzt sich dann das Puzzle zusammen, wobei es bis zum Schluß sehr spannend bleibt. Die Atmosphäre ist sehr düster und beklemmend. Die Story hat meiner Meinung nach wenig Haken und ist im Sinne von SF eingermaßen als möglich einzustufen, also nicht ganz abgedreht … 🙂 Der Film hat Parallelen zu Cargo und Sunshine, die ich beide auch sehr empfehlen kann. Über die Handlung will ich eigentlich gar nichts verraten, da eine sich entwicklende Story ihre Wirkung um so mehr entfalltet, je weniger man schon vorher weis. 5/5 Sternen.

Pandorum FSK 16 Version, keine SE

Tags: , ,
Posted in Movies | No Comments »

Callbacks sind nützlich, um Module voneinander zu entkoppeln. Die Module können miteinander kommunizieren, ohne voneinander zuviel wissen zu müssen. Kennen sich kolaborierende Klassen, leidet die Veränderbarkeit und Wartungsfreundlichkeit des Codes. Die Komplexität steigt und mit ihr dann auch die Zahl der Programmfehler. Auch die Wiederverwendbarkeit leidet unter “verzahntem” Code. Eine Möglichkeit der Entkoppelung besteht in der Verwendung von C++-Interfaces. Die Module kennen dann nur das zu verwendende Interface und die zu benutzede Addresse, sind aber nicht abhängig von dem dahinter stehenden Implementierer (der konkreten Klasse). Bei Verwendung der hier vorgestellten Methodik kennen die Module voneinder nur die zu verwendenden Signaturen und die Addresse, wo diese aufzurufen sind. Nicht abhängig sind diese Module dann von dem gerade zu benutzenden Besitzer, der diese Signatur zur verfügung stellt.

Mit boost::function und boost::bind kann man relativ einfach verzögerte Funktionsaufrufe realisieren. Nützlich sind diese für die gerade genannten Callbacks. Als Vorlage habe ich mir die mir am wichtigsten erscheinenden Fälle hier zusammengetragen:

Freie Funktionen

1
2
3
4
5
6
7
8
namespace free_functions
{
  void f_1()
  {
    boost::format f("%1%: called with args: void. It returned: void.");
    f % __FUNCTION__;
    std::cout << f.str();
  }

Diese freie Funktion kann man mit einem Objekt vom Type boost::function zu einem späteren Zeitpunkt (Callback) dann wie folgt aufrufen:

9
10
11
12
// Anlegen. Hier auf dem Stack. Für Objekte dann als member:
boost::function<void (void)> fn(boost::bind(&free_functions::f_1));
// Später zum Event-Zeitpunkt dann der Aufruf:
fn();

Als Template-Parameter (Zeile 11) übergibt man die Signatur der aufzurufenden Funktion. Hier: Return=void, 1.Argument=void. bind wird als Argument die Addresse der aufzurufenden Funktion übergeben (Zeile 11). Es baut dann den Funktor, den das function-object speichert. Über den überladenen Klammer-Operator kann dann die Funktion indirekt aufgerufen werden (Zeile 12).

Für eine freie Funktion mit Rückgabewert und Parameter der Art …

13
14
15
16
17
18
19
20
21
22
namespace free_functions
{
  int  f_2(double d, const std::string& s)
  {
    const int result = 1;
    boost::format f("%1%: called w. args: %2%, %3%. Returned: %4%");
    f % __FUNCTION__ % d % s % result;
    std::cout << f.str();
    return result;
  }

… sieht der Code zum Erzeugen und Aufrufen dann so aus:

13
14
15
16
17
18
19
20
21
22
23
// Erzeugen
boost::function<int (double, std::string)>
fn(
  boost::bind(
    &free_functions::f_2,
    _1, // bind-Platzhalter für ersten Parameter
    _2  // bind-Platzhalter für zweiten Parameter
));
 
// Der Aufruf:
const int result = fn(42.0,"hello world");

_1 ist ein Platzhalter für bind. Er weist bind an für die letzendlich aufzurufende Funktion als ersten Parameter den ersten Parameter des Funktor-Aufrufes, also dem überladenen Klammer-Operator des boost::function-Objektes, zu benutzen. Ist im vorliegenden Beispiel der double-Wert 42.0. Entsprechendes gillt für den zweiten Parameter. Die Reihenfolge bei bind entspricht der Reihenfolge der Parameter der auzurufenden Funktion, die Nummer des bind-Platzhalters der Reihenfolge der Parameter bei Aufruf des boost::function-Objektes. Definiert sind die Platzhalter _1 – _9. Ein Ändern der Reihenfolge oder das Nichtbeachten von Parametern ist möglich, so lange die Typen der Argumente zumindest konvertierbar sind:

24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
namespace free_functions
{
 // Argumente in der Reihenfolge austauschbar:
 void f_3(int a1, int a2, int a3)
  {
    boost::format f("%1% called. a1: %2%, a2: %3%, a3: %4%.");
    f % __FUNCTION__ % a1 % a2 % a3;
    std::cout << f.str() << std::endl;
  }
}
 
...
 
boost::function
fn_normal_order(
  boost::bind(
  &free_functions::f_3,
  _1,_2,_3));
 
boost::function
fn_mixed_order(
   boost::bind(
   &free_functions::f_3,
   _3,_2,_1));
 
fn_normal_order(1,2,3); // 1 2 3
fn_mixed_order(1,2,3);  // 3 2 1
fn_normal_order(1,2,"3");// (!) Fehler beim kompilieren

Member Funktionen

All das gerade Gesagte gillt auch für Member Funktionen von Objekten. Hier muß zusätzlich (natürlich) noch die Adresse des Objektes, bzw. die zu verwendende Instanz mitangegeben werden:

52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
namespace classes
{
  class A
  {
  public:
    ...
    // Definition der Methode:
    int  f_2(double d, const std::string& s)
    {
      const int result = 1;
      boost::format f(
        "%1% called. instance-addr: 0x%2%. d: %3%, s: %4%. return: %5%");
      f % __FUNCTION__ % this % d % s % result;
      std::cout << f.str() << std::endl;
      return result;
    }
 
    ...
 
// Anlegen der Instanz:
classes::A a;
 
...
 
// Definition des Callback-Objektes:
boost::function<int (double,std::string)> fn(
  boost::bind(
    &classes::A::f_2, // Methoden-Adresse
    &a, // Zusätzlich hier die zu verwendende Instanz mit angeben.
    _1,
    _2
));
 
...
 
// Irgendwann der indirekte Aufruf von a.f_2(...):
int result = fn(1.0,"hello member function");

In anderen Sprachen nennt man solche Funktionszeiger auf Memberfunktionen bestimmer Instanzen “Delegates”.

Source

Ein vc++2008-projekt mit dem Beispielprogramm kann hier downgeloded werden.
Boost-Version: 1.42 aufwärts. Proj.C++.Additional Include Directories:
Pfad zu boost angeben, Proj.Linker.General.Additional Library Directories:
Pfad zu boost\stage\lib angeben.

play_with_boost_function_vc++2008_proj

Ausblick

In einem weiteren Post werde ich noch aufzeigen, wie man mit boost::bind eine Member-Funktion von verschiedenen Instanzen einer Klasse in einer STL-Collection aufrufen kann.

Bis zum nächsten Post,

Daniel

Gotcha beim Erstellen von Dlls!   November 12th, 2010

Ich habe eine Dll mit visual C++ 2008 erstellt. Eine andere Applikation (auch mit mit Visual C++ 2008 erstellt) lädt diese über eine Ladelib am Programmanfang. Auf den meisten Rechnern (Entwicklerrechner) gings gut, bei wenigen Kunden kam die Meldung: “The application failed to initialize properly (0x……..). Click OK to terminate.”. Eine Recherche bei Google brachte viele Sachen mit falschen Rechten von Dlls, die Rechte der Dll waren aber in Ordnung. Die Lösung war dann: Die Applikation war gegen die statische Version der C-Runtime-Bibliothek gelinkt, die Dll gegen die Dll-Version. Auf Rechnern, wo beide kompatibel waren, ging es gut, auf Rechnern, wo die Versionen nicht zusammen passten, ging es schief. Die Lösung war dann, die Dll auch gegen die statische Version der C-Runtime zu linken. Schreibe das in der Hoffnung, das es jemanden hilft, der irgendwann mal auf das gleiche Problem trifft.

Bis dann,

Daniel