Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

Darkrel

Treue Seele

  • »Darkrel« ist der Autor dieses Themas

Beiträge: 143

Wohnort: Zürich

Beruf: Student ETH Zürich

  • Private Nachricht senden

1

24.01.2011, 11:00

ios_base register_callback mit pointer to member function versehen?

Hallo Leute!

Folgendes Problem:
In einer etwas aufwändigeren Log - Klasse soll man std::ostreams registrieren können, in die eine etwaige Ausgabe zusätzlich umgeleitet wird.
Aktuell sind zwei Stück nötig, die genauen Gründe lasse ich hier mal aussen vor.

Nochmal zum mitschreiben ein minimal Beispiel:

C-/C++-Quelltext

1
struct OstreamHolder {  std::ostream *listener_one; std::ostream *listener_two;};



Bevor Diskussionen zum Design aufkommen: Der obige code kommt so nicht bei mir vor ;) Aber das Problem ist das gleiche.

Weshalb pointer?

1. Polymorphismus
2. Da jemand nicht zwingend einen listener setzen _muss_, sollten die variablen auch auf NULL gesetzt werden können.

ios_base hat nun eine funktion register_callback, die einen Funktionen der folgenden Signatur registrieren lässt:
void (ios_base::event ev, ios_base& obj, int index)

Es ist nicht möglich einen Funktionszeiger auf eine Memberfunktion zu registrieren (ich erläutere hier nicht, weshalb :) ).
Meine Frage ist: Gibt es einen workaround, der es möglich macht eine Memberfunktion eines objektes über eine nicht - member funktion aufzurufen,
die exakt die obige Signatur hat?

Edit: Ich wäre froh, wenn dabei keine Objekte irgendwie in den globalen namespace gepappt werden bzw. generell objekte frei im code herumschwirren.

mfg, Dalhai
:cursing:

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

2

24.01.2011, 11:20

Wofür auch immer du das brauchst; dein Callback bekommt den von dir bei register_callback im Parameter index angegebenen Wert übergeben. Damit kannst du dir einen Mechanismus bauen der basierend auf diesem Wert ein Objekt bestimmt und eine Memberfunktion davon aufruft.

Darkrel

Treue Seele

  • »Darkrel« ist der Autor dieses Themas

Beiträge: 143

Wohnort: Zürich

Beruf: Student ETH Zürich

  • Private Nachricht senden

3

24.01.2011, 14:07

Da komme ich dann aber trotzdem nicht um ein Singleton - like objekt bzw. global oder im namespace verfügbares objekt drumherum, richtig?

Wofür ich das brauche:
Wenn ich einen zeiger / eine Referenz auf den übergebenen ostream speichere, dieser aber ausserhalb der verwaltenden struktur zerstört wird,
bekomme ich innerhalb der verwaltenden struktur zugriffsfehler, denn ich arbeite ja quasi mit einem nicht - existenten objekt weiter.

Ich muss irgendwie der verwaltenden struktur mitteilen, dass der ostream zerstört wurde.

Den ganzen Aufwand betreibe ich nur, damit das Ding auch wirklich Narrensicher wird. Ich könnte natürlich faul sein und sagen
"Wer auch immer die Struktur verwendet, der muss selber darauf achten, dass es keine Zugriffsfehler gibt!".
Aber ob das wirklich schön wäre ? :S

Das mit dem callback war so mein erster Gedanke, wie ich das problem lösen könnte. Der stream wird zerstört -> ein erase - event wird gesendet -> alle strukturen, die den ostream benutzen setzen den entsprechenden ostream auf NULL -> keine Zugriffsfehler.
:cursing:

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

4

24.01.2011, 14:09

Die beste Lösung wär da wohl dein System so zu bauen dass der ostream nicht einfach so zerstört werden kann ;)
Aber in deinem Fall würd ich es einfach crashen lassen wenn der ostream zerstört wird ohne dass er zuerst abgemeldet wird. Das liegt wenn du mich fragst ganz klar in der Verantwortung des Benutzers deiner Komponente, da würd ich nicht anfangen derartige Klimmzüge zu machen. Wenn der ostream flöten geht dann ist das ein Fehler im Programm, den muss man nicht mit aller Kraft verschleiern (wenn der ostream automatisch abgemeldet würde dann funktioiniert das Programm ja trotzdem noch nicht richtig, es stürzt nur nichtmehr ab). Wenn ich der Benutzer deines Log bin und versehentlich meinen ostream delete dann ist mir lieber es stürzt ab und ich merk dass ich versehentlich meinen stream deleted hab als es läuft weiter und ich wunder mich zwei Stunden lang warum es nichts logged.

Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von »dot« (24.01.2011, 14:17)


Darkrel

Treue Seele

  • »Darkrel« ist der Autor dieses Themas

Beiträge: 143

Wohnort: Zürich

Beruf: Student ETH Zürich

  • Private Nachricht senden

5

24.01.2011, 14:19

Na, wenn nur ich das System benutzen würde, wär das ja kein Problem ;) Da aber auch andere damit arbeiten werden, muss ich vom dümmsten anzunehmenden
User ausgehen. Und der könnte halt so etwas machen wie:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
OstreamHolder h; 
{
ofstream x; 
x.open("Test.txt"); 
h.SetListener(x); 
} //x ist zerstört, in h wird aber immernoch auf das nicht mehr existente x verwiesen 
h << "Test";
 


Was dann zu einem Fehler führt, da der stream innerhalb von h nicht auf NULL gesetzt wurde.

Edit: Hatte ich mir auch schon überlegt. Stimmt schon, man sollte offensichtliche Fehler nicht krampfhaft verstecken :)
:cursing:

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

6

24.01.2011, 14:25

Ich würd mich an deiner Stelle sowieso fragen ob es wirklich sinnvoll/notwendig ist beliebige ostreams zu registieren. Wenns nur um ein Log geht macht das imo alles nur unnötig kompliziert.

7

24.01.2011, 16:53

hm is die frage ob das was du da machst so schön ist. Nehm doch lieber std::cout und co und leite die in der main um ... von miraus in nen split_stream object, dass dir die ausgabe auf zwei streams umleitet.
Devil Entertainment :: Your education is our inspiration
Der Spieleprogrammierer :: Community Magazin
Merlin - A Legend awakes :: You are a dedicated C++ (DirectX) programmer and you have ability to work in a team? Contact us!
Siedler II.5 RttR :: The old settlers-style is comming back!

Also known as (D)Evil

Darkrel

Treue Seele

  • »Darkrel« ist der Autor dieses Themas

Beiträge: 143

Wohnort: Zürich

Beruf: Student ETH Zürich

  • Private Nachricht senden

8

24.01.2011, 17:14

Also hm... ich glaube wir sprechen hier aneinander vorbei :P
Es geht hier nicht darum ob meine Lösung schön ist oder nicht. Natürlich kann ich einfach cout umleiten, aber dann würde die Flexibilität, die gewünscht wurde flöten gehen.
Wir haben z.b. eigene Streams, die von ostream bzw. indirekt von ostream abgeleitet sind und Daten in speziellen Formaten speichern.

Die Anforderung ist einfach:
- So lange ein objekt von std::ostream ableitet, kann der input an dieses Objekt umgeleitet werden. Fertig.

Ich suche allerdings keine Lösung für dieses uneigentliche Problem, sondern ich suche eine Möglichkeit, einen Zeiger auf einen ostream per callback funktion auf NULL setzen zu lassen.
So lange der ostream existiert, wird der input an ihn umgeleitet. Wird der ostream zerstört (ios_base::event::erase wird gesendet), wird auch kein input mehr an ihn weitergeleitet. So die idee.

Edit: Ich hätte auch genauso gut schreiben können, wir wollen beliebig viele ostreams speichern, dann wäre aber vermutlich eine Diskussion entfacht um Polymorphismus in STL Containern.
:cursing:

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Darkrel« (24.01.2011, 17:26)


dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

9

24.01.2011, 19:22

Wie gesagt, ich denke immer noch dass die Ownership dieser Stream Objekte eigentlich nicht außerhalb der Log Klasse liegen sollte, die Tatsache dass sie das tut, du das aber eigentlich nicht willst ist ja gerade der Grund für all deine Probleme. Das Log kann ja intern alles mögliche verwenden, dem Anwender is das egal. Anstatt jetzt irgendwelche komplizierten Systeme zu konstruieren würd ich erstmal einen Schritt zurück machen und überlegen ob damit nicht nur Symptome behandelt werden...

Darkrel

Treue Seele

  • »Darkrel« ist der Autor dieses Themas

Beiträge: 143

Wohnort: Zürich

Beruf: Student ETH Zürich

  • Private Nachricht senden

10

24.01.2011, 20:27

Lösung des Problems (reduziert auf ein Minimum an Code):

C-/C++-Quelltext

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
class Log
{ 
       public:
              Log() : listener(NULL){};
              //Streams, die hinzugefügt werden, müssen vom typ Log::LogStream<BaseStream> sein.
              //ein Log::LogStream<ofstream> ist also z.B. abgeleitet von ofstream.
              //struct, weil ich zu faul war um hier fürs Forum auch noch auf private / public zu achten ;) Für das Beispiel hier reichts.
              template<typename BaseStream>
              struct LogStream : public BaseStream
              {
                     //Standardkonstruktor. Initialisiert die beiden Pointer mit NULL.
                     LogStream() : destroy(NULL), log(NULL){};

                     //Destruktor. Virtuell, zur sicherheit, falls ein vom LogStream abgeleiteter stream eingefügt werden soll.
                     //Das Herz des Memberpointer callback mechanismus. Da der destruktor von ios_base (und damit auch der von ostream) virtuell ist,
                     //kommt es nicht zu object slicing, auch nicht wenn ein LogStream objekt über einen BaseClass pointer verwaltet wird.
                     virtual ~LogStream()
                     {
                            if(log != NULL && destroy != NULL) {
                                   ((*log).*destroy)();
                                    log = NULL;
                            }
                     };
                     
                     //Signatur des Callbacks.
                     typedef void (Log::*Callback)(void);

                     //Der Callback. In der fertig designten version wird natürlich auch auf Kapselung geachtet. Bitte hier nicht weiter darauf rumhacken.
                     Callback destroy;

                     //Da wir einen memberpointer verwenden, muss auch das objekt, auf welchem wir die funktion aufrufen vorhanden sein.
                     Log *log;
              };
              
              //Template funktion, die LogStreams mit beliebigem base stream als listener hinzufügt.
              //Dank ADL (Argument dependent lookup), muss der template parameter nicht explizit benannt werden.
              template<typename BaseStream>
              void AddListener(LogStream<BaseStream> &stream)
              {
                     if(listener != NULL && !listener->bad()) {
                            listener->flush();
                     }
                     listener = &stream;

                     //Callback setzen
                     stream.destroy = &Log::DisableListener;
                     stream.log = this;
              };

              void CheckStream()
              {
                  if(listener == NULL) cout << "Listener is NULL" << endl;
                  else cout << "Listener is not NULL" << endl;
              };
       private:
              //Hier wird meine Anforderung erfüllt. Damit nicht jeder x beliebige Typ als BaseStream verwendet werden kann,
              //muss der LogStream (und damit der BaseStream) in einen ostream konvertierbar sein. Für alle Typen, die diese
              //anforderung nicht erfüllen, spuckt der compiler einen Fehler aus. (Remember, Fehler zur compile time = "guter" fehler).
              ostream       *listener;
              
              //Die Callback funktion, die den listener auf NULL setzt.
              void DisableListener()
              {
                     if(listener != NULL && !listener->bad()) listener->flush();
                     listener = NULL;
              };
};
 


Nachteile:
- Wer in einer klasse einen ostream conversion operator definiert, und diesen typ dann als "BaseStream" template parameter übergibt, nur um Fehler und unerwünschtes verhalten hervorzurufen, der hat einen an der Waffel ;)
Edit: Diesen Fall kann man durch ein kleines Template allerdings schon zur compiletime abfangen.
- Man kann keine std::ostreams mehr direkt hinzufügen, sondern muss objekte vom typ Log::LogStream<BaseStream>, mit BaseStream typ, der von ostream erbt oder eine implizite konvertierung erlaubt instanzieren.

Beispielcode:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
int main(int argc, char** argv)
{
    Log l;
    l.CheckStream(); 
    {
          Log::LogStream<ofstream> t;
          l.AddListener(t);
          l.CheckStream();
    } //t wird hier zerstört.
    l.CheckStream();
    system("pause");    
};
:cursing:

Dieser Beitrag wurde bereits 9 mal editiert, zuletzt von »Darkrel« (24.01.2011, 22:24)


Werbeanzeige