Zum Inhalt springen

Changelog mit template für geänderte Variablen


PL1994

Empfohlene Beiträge

Hallo,

ich versuche mich gerade an einem Changelog, das, sobald eine Variable eines beliebigen Typs geändert wird, ebendiese Änderung mit einem Timestamp als Key in einer "map" abspeichern soll. Um nicht an einen Datentyp gebunden zu sein, habe ich eine Klasse "Change" als template geschrieben, die jeweils neuen und alten Wert der Variable beinhalten soll (die Variable, auf die sich die Änderung bezieht, wird noch nicht referenziert, was für mein Problem aber auch egal ist). Das ganze nutzt mir natürlich herzlich wenig, wenn ich mich bei der Definition der "map" dann doch auf einen Datentyp festlegen muss.

Ich hatte mir das so vorgestellt:

#include <ctime>
#include <iostream>
#include <map>

using namespace std;

template<class T> class Change
{
	T n;
	T o;

public:
	Change(T n, T o) : n(n), o(o) {}

	T getN() { return n; }
	T getO() { return o; }
};

map<time_t, Change< /* ??? */ >> changelog;

int main()
{
	changelog.insert(pair < time_t, Change<int>>(time(NULL), Change<int>(0, 0)));
}

Die mit "???" markierte Stelle ist mein Problem. Kann ich da irgendetwas einsetzen, um den Datentyp, der hinterher tatsächlich verwendet werden soll, noch offen zu lassen oder geht das sowieso nicht?

Die einzige Alternative, die mir einfällt, ist "Change" (dann nicht als template) so umzuschreiben, dass die Klasse für alle möglichen Datentypen Variablen für jeweils neu und alt bereitstellt und entsprechende Konstruktoren. Das dürfte aber ziemlich aufwendig werden. Gibt es eine andere (einfachere) Lösung?

Gruß

PL1994

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hab ewig nichts mehr mit template Programmierung in C++ gemacht, aber wenn du beliebige Typen in deine Map einfügen willst, welche ja Typengebunden ist, brauchen alle deine Typen eine gemeinsame Basis.

z.B. indem du eine abstrakte Klasse erstellst welche als Interface dient von dem Change ableitet. Diese Klasse solltest du dann als Typ für die map verwenden können.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Danke dir für die Antwort. Wenn ich das jetzt richtig verstanden habe, meinst du das so:

#include <ctime>
#include <iostream>
#include <map>

using namespace std;

class AbstractChange
{

};

class IntChange : public AbstractChange
{
public:
	int newVal;
	int oldVal;

	IntChange(int newVal, int oldVal) : newVal(newVal), oldVal(oldVal) {}
};

class CharChange : public AbstractChange
{
public:
	char newVal;
	char oldVal;

	CharChange(char newVal, char oldVal) : newVal(newVal), oldVal(oldVal) {}
};

map<time_t, AbstractChange> changelog;

int main()
{
	changelog.insert(pair < time_t, AbstractChange>(time(NULL), IntChange(0, 0)));
}

Dann bräuchte ich aber für alle Datentypen eine eigene Klasse, die jeweils eine Änderung von einer Variable dieses Typs behandelt. Das wäre dann noch aufwendiger, als in einer Klasse "Change" von allen Datentypen Variablen für jeweils neu und alt bereitzustellen. Habe ich das falsch verstanden?

Link zu diesem Kommentar
Auf anderen Seiten teilen

Habe noch etwas anderes ausprobiert. Das geht im Prinzip. Ich kann mir nur fast nicht vorstellen, dass das keine Nachteile mit sich bringen soll:

#include <ctime>
#include <iostream>
#include <map>

using namespace std;

typedef long Value;

class Change
{
public:
	Value v1, v2;

	Change(Value v1, Value v2) : v1(v1), v2(v2) {}
};

map<time_t, Change> changelog;

int main()
{
	changelog.insert(pair < time_t, Change>(0, Change((Value)"Val1", (Value)"Val2")));

	cout << (char*)changelog.at(0).v1 << "\n" << (char*)changelog.at(0).v2 << endl;
	system("pause");
}

Was haltet ihr davon?

Link zu diesem Kommentar
Auf anderen Seiten teilen

Nein, so

class AbstractChange
{
};

template<class T> class Change : public AbstractChange
{
	T n;
	T o;

public:
	Change(T n, T o) : n(n), o(o) {}

	T getN() { return n; }
	T getO() { return o; }
};

map<time_t, AbstractChange> changelog;

int main()
{
	changelog.insert(pair < time_t, Change<int>>(time(NULL), Change<int>(0, 0)));
}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 21 Stunden, PL1994 sagte:

Aber wie komme ich jetzt hinterher an die Daten aus "Change" wieder heran?

Gar nicht. Hier passiert etwas, das "Slicing" genannt wird.

Du hast eine map<time_t, AbstractChange>. Du kannst hier Objekte von Klassen ablegen, die von AbstractChange erben, da hier eine implizierte Konvertierung stattfindet. Aber die map speichert wirklich nur AbstractChange-Objekte. Alles, was durch Vererbung dazukommt, wird abgeschnitten (daher der Name des Effekts).

Folgende Dinge müssen dir klar sein:

  • Verschiedene Instanzen eines Klassentemplates sind nicht irgendwie "verwandt". Change<foo> und Change<bar> sind zwei getrennte, komplett unterschiedliche Klassen. Die Typen sind nicht ineinander umwandelbar. Selbst wenn es eine implizite Konvertierung zwischen foo und bar selbst gibt. Es gibt keine Gemeinsamkeit, auf Basis derer eine Zusammenfassung stattfinden könnte (außer du baust eine gemeinsame Basisklasse ein).
  • Der Typ eines jeden einzelnen Ausdrucks im Code steht zur Compile-Zeit fest.

Du könntest mit Smartpointern arbeiten, oder Sammeltypen wie boost::any benutzen.

Grundsätzlich würde ich aber zunächst einmal klären, was du mit den gesammelten Daten überhaupt machen willst.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Bevor ich mich mit Smartpointern auseinandersetze, erkläre ich dann wohl erst einmal, was ich mit den Daten vorhabe:

Es geht darum, dass der Benutzer zum Beispiel Änderungen an einer TreeView vornehmen kann oder bestimmte Datensätze bearbeitet etc. Diese Daten werden in einer Datenbank gespeichert. Damit jetzt nicht jedes mal, bei jeder noch so kleinen Änderung gleich die Datenbank bearbeitet werden muss, wollte ich ebendiese Änderungen in einem Changelog speichern, um dann, wenn gewünscht, alle auf einmal in die Datenbank zu übertragen.

Außerdem sollte man eine Aktion auch noch rückgängig machen können. Diese könnte ich aber bei einer bloßen Änderung an der Datenbank nicht mehr nachvollziehen. Das wären die zwei Hauptfunktionen. Das muss aber eben mit verschiedenen Datentypen gehen ...

Link zu diesem Kommentar
Auf anderen Seiten teilen

Du könntest das schon so machen wie oben, müsstest dann aber einen Pointer auf eine AbstractChange Struktur in der Map halten (und dafür sorgen das dieser gültig bleibt).

 

Das Ganze führt dich aber noch zu einem ganz anderem Problem. Woher willst du wissen was für ein Objket du später wider aus deiner Map rausholst? Also ob das jetzt ein Change<int> oder ein Change<string> oder irgendwas ganz anderes das von AbstractChange ableitet ist?

Bei dem was du machen willst, macht es imho keinen Sinn das auf so eine abstrakte, generische Implementierung herunterzubrechen.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Na gut, dann eben anders. Ich werde es wohl jetzt wie folgt machen :

#include <ctime>
#include <iostream>
#include <map>
#include <string>

using namespace std;

class Value
{
private:
	enum Type
	{
		INTEGER, STRING
	} type;

	const int intValue;
	const string stringValue;

public:
	Value(int value) : type(INTEGER), intValue(value) {}
	Value(string value) : type(STRING), intValue(NULL), stringValue(value) {}

	int getIntValue() { return intValue; }
	string getStringValue() { return stringValue; }
};

class Change
{
private:
	Value newValue, oldValue;

public:
	Change(Value newValue, Value oldValue) : newValue(newValue), oldValue(oldValue) {}

	Value getNewValue() { return newValue; }
	Value getOldValue() { return oldValue; }
};

map<time_t, Change> changelog;

int main()
{
	changelog.insert(pair<time_t, Change>(time(NULL), Change(Value(1), Value(2))));
	changelog.insert(pair<time_t, Change>(time(NULL)+1, Change(Value("string1"), Value("string2"))));

	for (map<time_t, Change>::iterator i = changelog.begin(); i != changelog.end(); i++)
		cout << i->first << ": " << i->second.getOldValue().getIntValue() << " -> " << i->second.getNewValue().getIntValue() << " | " << i->second.getOldValue().getStringValue() << " -> " << i->second.getNewValue().getStringValue() << endl;

	system("pause");
}

Das geht prinzipiell zwar, ist aber halt ungünstig, weil bei "Value" immer eine völlig sinnlose Variable gespeichert ist und man später erst mal herausfinden muss, was überhaupt gespeichert werden sollte.

Das scheint mir nicht gerade optimal zu sein. Für Verbesserungsvorschläge bin ich also absolut offen :)

EDIT: Nicht fragen, warum da die Variable "type" drin ist. Wollte was anderes versuchen, was aber Quatsch war. Nur vergessen, das rauszulöschen ...

Bearbeitet von PL1994
Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 24 Minuten, PL1994 sagte:

Das geht prinzipiell zwar, ist aber halt ungünstig, weil bei "Value" immer eine völlig sinnlose Variable gespeichert ist und man später erst mal herausfinden muss, was überhaupt gespeichert werden sollte.

Das Problem hättest du bei deinem Ansatz in jedem Fall. Wenn du unterschiedliche Typen zusammenwirfst, musst du sie hinterher wieder aufwändig unterscheiden (wenn es keine sinnvolle Vererbungshierarchie gibt).

Ich verstehe auch nicht, was dir dieses Changelog überhaupt bringt. Du weißt damit, dass zum Zeitpunkt X ein int von 1 auf 2 geändert wurde. Was nützt dir das? Weder kannst du daraus herleiten, welcher Datensatz geändert werden muss, noch welches Feld des Datensatzes (wenn es denn mehr als ein int-Feld gibt).

Und wie willst du Lösch- und Einfügeaktionen abbilden?

Du solltest die Änderungsverfolgung nicht auf Basis einzelner ints und Strings modellieren, sondern auf den Objekten, die deinen Datensätzen entsprechen. Und da ist es durchaus sinnvoll, für jede Art von Objekt eine separate Änderungsverfolgung zu haben.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich habe das nur auf das Wesentliche reduziert. Natürlich muss in der Klasse "Change" noch gespeichert werden, welches Objekt geändert wurde. Da lag nur nicht mein Problem.

Das würde ich dann mit Hilfe einer Enumeration machen, deren Member beschreiben, welche Aktion vorgenommen wurde. Eine Variable muss noch den Primärschlüssel speichern. Dann reichen einzelne Integer und Strings völlig und ich brauche nicht Objekte, die den ganzen Datensatz widerspiegeln, in "Change" speichern, obwohl nur ein einzelnes Feld eines Datensatzes geändert werden soll ...

Löschen und Einfügen brauche ich nicht, sondern nur die Möglichkeit, eine Aktion rückgängig zu machen. Dafür reichen die Daten aus.

Ich wollte nur vermeiden, dass in der Klasse "Value" zwangsläufig immer eine Variable des Wertes "NULL" steht.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Dein Kommentar

Du kannst jetzt schreiben und Dich später registrieren. Wenn Du ein Konto hast, melde Dich jetzt an, um unter Deinem Benutzernamen zu schreiben.

Gast
Auf dieses Thema antworten...

×   Du hast formatierten Text eingefügt.   Formatierung wiederherstellen

  Nur 75 Emojis sind erlaubt.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Editor leeren

×   Du kannst Bilder nicht direkt einfügen. Lade Bilder hoch oder lade sie von einer URL.

Fachinformatiker.de, 2024 by SE Internet Services

fidelogo_small.png

Schicke uns eine Nachricht!

Fachinformatiker.de ist die größte IT-Community
rund um Ausbildung, Job, Weiterbildung für IT-Fachkräfte.

Fachinformatiker.de App

Download on the App Store
Get it on Google Play

Kontakt

Hier werben?
Oder sende eine E-Mail an

Social media u. feeds

Jobboard für Fachinformatiker und IT-Fachkräfte

×
×
  • Neu erstellen...