[Template]Property-Klasse: Problem mit Zeiger

Hallo liebe Community.
Ich bin etwas neu in C++, und kenne auch vieles nur aus C++/CLI.
Aus C++/CLI bin ich auch das verwenden von Properties gewohnt.
Nun, da ich schon ein wenig mit templates (Oder in C++/CLI halt generic) gearbeitet habe, dachte ich, warum schreib ich nicht einfach eine Klasse dafür.

An sich scheint alles richtig zu sein, keine Compilerfehler, Anwendung lässt sich normal starten, aber mein Programm schmiert beim debuggen immer ab mit folgender Fehlermeldung:
Code:
Program Received signal SIGSEGV
Stack Trace is available in the "Call Stack" tab

Folglich alle Hintergrundinformationen:
IDE: CodeLite
Code:

main.cpp
C++:
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include "Property.hpp"

using namespace std;

int main(int argc, char **argv)
{
	int EinInt = 0;
	Property<int> EinIntProperty(&EinInt);
	EinIntProperty = 15; /* <= In dieser Zeile wird die Set Funktion aufgerufen (siehe unten, Properties.tpp) */
	int EinWeiteresInt = EinIntProperty;
	printf("%d", EinWeiteresInt);
	return 0;
};
Property.hpp:
C++:
#ifndef PROPERTY_H
#define PROPERTY_H

template <class ValueType>
class Property
{
	private:
		ValueType* _value;
		Property(){*_value = 0;};

	public:
		Property(ValueType* Target);
		Property(ValueType Value);
		ValueType Get();
		void Set(ValueType Value);
		operator ValueType();
};

#include "Property.tpp"
#endif
Property.tpp:
C++:
template<class ValueType>
Property<ValueType>::Property(ValueType* Target)
{
	_value = Target;
};

template<class ValueType>
Property<ValueType>::Property(ValueType Value)
{
	Set(Value);
};

template<class ValueType>
ValueType Property<ValueType>::Get()
{
	return *_value;
};

template<class ValueType>
void Property<ValueType>::Set(ValueType Value)
{
	*_value = Value; /* <= Hier tritt der Fehler auf! */
};

template<class ValueType>
Property<ValueType>::operator ValueType()
{
	return Get();
};

Per Google habe ich herausgefunden, dass es irgendwas mit Speicherzugriff zu tun hat. Allerdings habe ich keine Ahnung, wie ich es beheben kann.

PS: Bitte stellt den Sinn dieser Klasse nicht in Frage, ich benötige sie nur übergangsweise zum Lernen von purem C/C++ und brauchte etwas zum anfangen. Diese Klasse hat also einen Lernfaktor im Fokus, anstatt eines wirklichen Sinnes.

Gruß, JustShinigami
 
Hi

Nun, da ich schon ein wenig mit templates (Oder in C++/CLI halt generic) gearbeitet habe
Großer Unterschied.

In welcher Zeile passiert der Absturz?

edit:
Um etwas ausführlicher zu werden:
Bei Generics hast du Compilerchecks und zu einem gewissen Grad dynamische,
bei der Compilezeit unwichtige, Sachen drin (erst zur Laufzeit müssen sie bestimmt werden)

Templates sind nichts anderes, als die ganze Klasse x-mal zu kopieren und für den Valuetype
immer den passenden Typ einzutragen. Nur macht das der Compiler automatisch.
Wenn die Templateklasse nicht verwendet wird werden zB. auch
keine offensichtlichen Syntaxfehler gemeldet, die man eventuell drin hat.
Und alles muss zur Compilezeit feststehen.


Fehler, die ich sehe:
Property.hpp:
Zeile 9: Der * gehört weg. Du willst doch die Adresse auf 0 setzen (bzw. " (void*)0 ")
Ist ein möglicher Absturzgrund (bzw. wird sicher abstürzen, nur ob das der einzige Fehler ist...)

Der Operator macht bei Pointertypen Probleme.

Warum bei den ValueType-Parametern/Returntype nicht mit Referenzen arbeiten?
(kann natürlich auch ohne Sinn machen, soll nur ein Denkanstoß sein)


Property.tpp:
Ah, da ist ja ein Kommentar zur Fehlerstelle.
Sorry, zuerst übersehen.
Fehler: Du hast Pointer nicht verstanden.
C++:
_value = &Value;
 
main.cpp, Zeile 12 -> Property.tpp, Zeile 22 (Ist im übrigen auch in meinem Code kommentiert, wo der Fehler passiert ;) )

PS: Was ist denn der Unterschied zwischen templates und generic? o_O
 
Ah, Danke für die Erklärung :)
Code:
_value = &Value;
Ne, eben nicht :eek:
ich will dem pointer keine neue adresse zuweisen, sondern den Wert innerhalb der alten adresse ändern, oder komm ich da aufs selbe hinaus? Am besten ich verdeutliche den Sinn meiner Property Klasse:
Wie es nicht sein soll:
C++:
class test
{
    private:
        int _EineZahl;
        
    public:
        Property<int> EineZahl(_EineZahl);
        void ÄndereEineZahl(int NeuerWert)
        {
            _EineZahl = NeuerWert;
        }
}
test TestObjekt(); // _EineZahl ist 0 und EineZahl kopiert die 0 in den eigenen Speicher
TestObjekt.ÄndereEineZahl(15); // _EineZahl ist nun 15 und EineZahl ist 0
TestObjekt.EineZahl = 20; // _EineZahl ist noch 15 und EineZahl nun 20
Wie ich mir dachte wie es klappt:
C++:
class test
{
    private:
        int _EineZahl; // <= Auf diese interne Variable soll die Property zeigen und zugreifen
        
    public:
        Property<int> EineZahl(&_EineZahl);
        void ÄndereEineZahl(int NeuerWert)
        {
            _EineZahl = NeuerWert;
        }
}

test TestObjekt(); // _EineZahl ist 0 und EineZahl auch 0
TestObjekt.ÄndereEineZahl(15); // _EineZahl ist nun 15 und EineZahl ist auch 15
TestObjekt.EineZahl = 20; // _EineZahl ist 20 und EineZahl auch 20

PS: Wenn dein vorgeschlagener Code trotzdem richtig ist, entschuldige ich mich für deinen Aufwand, bin halt noch relativ neu in C++ :s

Gruß, JustShinigami
 
Ok, nochmal sorry, ich hab Unsinn geschrieben.
(zumindest im Teil über Pointer. Das mit den Templates ist iO.)
Hab zu wenig nachgedacht, was das Ganze eigentlich ist...

Hmm...selber mal kompilieren...

edit: Ok, ich bin einfach dumm.
Dir fehlt der operator=
(Konstruktor, CC-Konst. und operator= bei solchen Datensachen immer machen).

Detaillierter:
Die Zuweisung im main erstellt mit dem Konstruktor mit dem ValueType-Parameter
eine zweite Instanz der Klasse, will für diese laut Konstruktorcode Set aufrufen,
und dann die Instanz zwei der Ersten zuweisen.
Die zweite Instanz hat keinen gültigen Pointer in _value...

Um zu verhindern, dass der Compiler selbst macht was er will,
kann man eben auch festlegen, was bei = passiert (wenn es um ein Objekt dieser Klasse geht).
zB.
C++:
Property<ValueType> &Property<ValueType>::operator=(const ValueType &Value)
{
    Set(Value);
    return *this;
};
Erste Zeile:
"Property<ValueType>::eek:perator=" ist trivial.

Parameter "ValueType &Value": Warum &: Nennt sich Referenz und übergibt keine Kopie,
sondern das Original (vom Prinzip her ein Pointer, bei dem man sich das ewige *-Schreiben
spart (und auch das & beim tatsächlichen Übergeben),
aber dafür nicht so allgemein verwenden kann. Reine Bequemlichkeit.)
Wichtig ist das Nicht-Kopieren-lassen uA. bei solchen seltsamen klassen,
die in ihrem Copykonstruktor andere Sachen machen als eine 1:1-Kopie zu erzeugen.
(aus diesem Grund wäre es ratsam, allen Klassenmethoden hier,
die ein pointerloses Objekt bekommen, eine Referenz zu geben)

Warum const: "=15" ist ein fixer int-Wert.
Wenn es eine Variable wäre, die auch geändert werden kann
(zB. in der Funktion, wo sie hin übergeben wird...)
Man muss eben festlegen, dass das übergebene Ding in der Funktion nicht geändert wird
(weil das mit dem fest reinkompiliertem Wert "15" nicht gut gehen würde)

Und der Returnwert:
Zurzeit kann man ein "a=b;" machen.
Es soll aber auch "a=b=c=d;" möglich sein (ist in C/C++ uÄ. eben erlaubt,
muss auch bei Klassen funktionieren).
In dem Fall würde zuerst c=d gemacht, und das "Ergebnis" davon wird dann b zugewiesen usw.
Damit es ein Ergebnis der Zuweisung gibt: Returnwert,
und zwar gibt der Operator der von c sein eigenes Objekt zurück
(wieder das Original, statt eine Kopie erzeugen zu lassen).


Rest: Set eben, und dann das schon angesprochene Return.

...das hat dich jetzt wahrscheinlich sehr verwirrt
(ich kenn mich schon selbst nicht mehr aus),
also...Fragen gerne gesehen :D
 
Wow, danke erstmal für die ausführliche, wenn auch verwirrende, und vor allem schnelle Antwort!
Ehrlich gesagt habe ich ein problem, deinem Text zu folgen, was aber warscheinlich an der Uhrzeit liegt :D
Ich werde jetzt mal eben deine Punkte überprüfen und in meinem Code abarbeiten. Und dabei dachte ich, dass sich so eine Klasse eben fix schreiben lässt ... Man lernt eben nie aus ^^
Werde gleich nochmal das Ergebnis meiner Arbeit hier reineditieren, damit ggf weitere Probleme behoben werden können oder die Lösung an andere weitergegeben werden.

EDIT:
Kannst du mir die Konventionen von C++ (Namensgebung etc) kurz erklären oder mir einen Link schicken, wo ich diese nachlesen kann?

EDIT2:

So sieht mein Code nun aus, der auch funktioniert! Also hier die Lösung für andere, die ein ähnliches Problem haben!

Property.hpp:
C++:
#ifndef PROPERTY_H
#define PROPERTY_H

template <class ValueType>
class Property
{
	private:
		ValueType* _value;
		Property(){*_value = 0;};

	public:
		Property(ValueType* Target);
		Property(ValueType Value);
		ValueType Get();
		void Set(const ValueType &Value);
		operator ValueType();
		Property<ValueType> &operator=(const ValueType &Value);
};

#include "Property.tpp"
#endif

Property.tpp:
C++:
template<class ValueType>
Property<ValueType>::Property(ValueType* Target)
{
	_value = Target;
};

template<class ValueType>
Property<ValueType>::Property(ValueType Value)
{
	Set(Value);
};

template<class ValueType>
ValueType Property<ValueType>::Get()
{
	return *_value;
};

template<class ValueType>
void Property<ValueType>::Set(const ValueType &Value)
{
	*_value = Value;
};

template<class ValueType>
Property<ValueType>::operator ValueType()
{
	return Get();
};

template<class ValueType>
Property<ValueType> &Property<ValueType>::operator=(const ValueType &Value)
{
    Set(Value);
    return *this;
};

(Getestet mit folgendem Code) main.cpp
C++:
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include "Property.hpp"

using namespace std;

int main(int argc, char **argv)
{
	int EinInt = 0;
	Property<int> EinIntProperty(&EinInt);
	printf("%d\n", EinIntProperty.Get());
	EinIntProperty = 15;
	printf("%d\n", EinIntProperty.Get());
	printf("%d", EinInt);
	return 0;
};

In diesem Sinne:
Göttliche Grüße, JustShinigami
 
Zuletzt bearbeitet:
Ich schreib das geordnet noch einmal...
du hast Recht, das Problem ist die Uhrzeit, aber das ist ein Problem bei mir :D
 
Nicht nur bei dir, ich fange um diese Uhrzeit meist schlecht an zu lesen, muss in 6 Stunden schon wieder zur Arbeit aufstehen ^^"
Noch mal ein fettes Danke für die Zeit und Arbeit, die du für mich investiert hast!! Ich sehe das nicht als selbstverständlich an, so viel für einen Neuankömmling zu tun! :)
Problem erledigt!
 
Also, es gibt drei Sachen, die jede C++-Klasse,
die irgendwas mit Datenspeichern zu tun hat, haben sollte:

1) Konstruktor. Was das ist, ist klar; aber genauer:
Parameterloser Konstruktor (oder explizites verbot davon)
Also entweder ein wirklicher parameterloser Konstruktor,
oder selbiges zB. in private wie bei dir (damit nicht verwendbar).
Wenn man nämlich einfach nichts dergleichen macht erstellt der
Compiler automatisch einen dazu (der nichts tut, aber man kann
dann Objekte ohne K.-Parameter erstellen. Was man evt. nicht will)


2a) Copy-Konstruktor (ab jetzt kurz CC)
Ist im Prinzip ein normaler Konstruktor,
der ein Objekt der eigenen Klasse als Parameter bekommt.
C++:
class A
{
public:
    A(A dasA)
    {...}
};
(Achtung, nicht kopieren, Fehler).
Der SInn dahinter ist, ein Objekt zu erstellen,
das inhaltlich eine Kopie vom übergebenen Objekt darstellt.

Kann eine 1:1-Kopie der Variablen drin sein, eventuell macht
aber etwas andere mehr Sinn...je nache Klasse.
Zb. wenn drin ein Pointer ist, dem mit malloc/new Speicherplatz reserviert wurde.
enn man das einfach mit = kopiert teilen sich dann beide Objekte den selben Speicher.
Für das neue Objekt einen eigenen Speicher reservieren und den Inhalt kopieren...

Wann der CC verwendet wird:
Man kann ihn einerseits wie einen normalen Konstruktor selbst im Code einsetzen,
andererseits gibt es Spezialfälle:
Zb. wenn man ein Objekt der Klasse als Parameter übergibt (oder bei Returnwerten...)
Übergibt man ein int ohne Pointer etc. hat man in der Funktion eine Kopie vom int-Wert.
Übergibt man ein Objekt einer Klasse...wird eine Kopie erstellt,
und zwar so, wie der CC es macht.


2b)
Wie kann man Parameter übergeben, drei Möglichkeiten:
Normal: Kopie wird übergeben

Pointer*: Kopie der Addresse, Variable selbst wird nicht kopiert (auch kein CC-Einsatz).

Referenz&: Auch keine Kopiererei, aber man braucht beim Aufruf
kein & und beim verwenden innen kein *
Dafür gibt es Einschränkungen, verglichen zu Pointern, die aber hier unwichtig sind.

Ein echter CC schaut dann so aus:
C++:
class A
{
public:
    A(A &dasA)
    {...}
};
Der Parameter wird als Referenz übergeben, also das unkopierte original, aber sternloser Zugriff.
Warum muss das sein?
Der CC soll das vorhandene dasA kopieren
(bzw. dem gerade neu erstellen Objekt Werte anhand von dasA zuweisen)
Der CC wird automatisch eingesetzt, wenn Parameterkopien erstellt werden müssen...
merkst? Wenn man dem CC einen Parameter gibt, der kopiert werden muss,
muss dafür der CC verwendet werden, der wieder eine Parameterkopie braucht...
also darf hier keine Kopie erstellt werden -> Referenz.


Allgemein sind Referenzen auch sonst sinnvoll, wenn man Kopien vermeiden will
zB. bei Parametern vom Templatetyp. Wenn das ein int ist störts nicht,
wenn das aber eine Klasse ist, bei der jedes Objekt 100MB braucht
(und deren CC alles 1:1 kopiert...)
wenn man auf die Kopie verzichten kann/will, dann kann man das dem Computer
auch ersparen, eine zu machen.


3a)
Der Operator = sollte neben Kontruktor und CC auch vorhanden sein.
Wie der Name sagt: Wie werden Zuweisungen zu einem Objekt x behandelt
C++:
MeineKlasse a, b;
a = b;
a = 4.6;
a = "blub";
...
Intuitiv würde man sagen, dass bei der ersten Zueisung b 1:1 in a kopiert wird,
und die anderen gehen so nicht, weil die Werte keine Objekte von MeineKlasse sind.

Was bei der Zuweisung 1 passiert kann man jetzt wieder steuern,
und auch andere Typen erlauben und entsprechend behandeln
Zuerst ein operator, der für den float-Wert passt (für deine Templateklasse passend):
C++:
Property<ValueType> &Property<ValueType>::operator=(float f)
{
    ...
    return *this;
}
Warum der Returntyp/wert:
Zuweisungsketten wie
C++:
a = b = c = 4.6;
sollen erlaubt sein.
Hier nimmt zuerst c das 4.6 in sich auf, dann b=c, dann a=b
(die Eigentypzuweisung später)
Der Returntyp wieder als Referenz, um keine unnötigen Kopien zu erzeugen.


3b)
Bei obigem float-Zuweisungs-Operator wurde vom float eine Kopie übergeben.
Angenommen, man macht das auch als Referenz,also kopielos:
C++:
Property<ValueType> &Property<ValueType>::operator=(float &f)
{
    ...
    return *this;
}
Wenn man f jetzt in der Funktion ändert ändert sich auch das Ding draußen.
Mit einem Klassenobjekt a:
C++:
float f;
...
a = f; //in Ordnung
a = 4.6; //nicht in Ordnung.
//Wenn man f drinnen ändert, wie soll man den festkompilierten Wert 4.6 ändern?
a = f+1; //auch nicht in Ordnung
Mit so einem Operator könnte man nur float-Variablen übergeben,
keine Einzelwerte, Rechnungen etc.
Will man das aber, muss man garantieren, den Parameter nicht zu ändern:
C++:
Property<ValueType> &Property<ValueType>::operator=(const float &f)
{
    ...
    return *this;
}
(oder eben wieder zur Kopievariante zurück).


3c)
Wenn man Objkte der eigenen Klasse übergeben will
(und unnötige Kopien vermeiden will)
wird das eben
C++:
Property<ValueType> &Property<ValueType>::operator=(Property<ValueType> &xyz)
{
    ...
    return *this;
}




So.
Wozu ist es dann gut, diese drei Spezialfunktionen immer zu machen?
Wie schon gesagt haben alle drei Situationen, wo sie verwendet werden,
auch wenn man sie nciht absichtlich aufruft.
Fehlen die Implementierungen macht der Compiler, was er für richtig hält
(es bleibt oft beim armseligen versuch)

Die Problemzeile ganz oben ist
C++:
EinIntProperty = 15;
Was passieren soll: *_value innen soll auf 15 gesetzt werden.

Wie es passieren sollte: operator=(ValueType diezahl) wird aufgerufen
und macht das drinnen für den _value-Pointer seines Objekts.

Was der Compiler macht, da es den operator= im Code nicht gibt:
Es gibt einen Konstruktor Property<ValueType>::property(ValueType Value)
Parameter passt dazu...also wird zuerst ein neues (zusätzliches)
Objekt der Klasse gemacht, mit dem Konstruktor, Parameterwert 15.
Dann würde noch sowas da sein:
C++:
EinIntProperty = dasneueObjekt;
Da beide Dinge Objekte der selben Klasse sind
kann der Compiler einen operator= selbst machen, der alles 1:1 kopiert.

Was dann beim Ausführen danebengeht:
Besagter Konstruktor, der mit 15 aufgerufen wird,
macht ein Set(15). Dieses macht ein "*_value = 15;"
Im temporären neuen Objekt, das keinen gültigen Pointer hat.
...
Wenn man einen eigenen operator= angibt ist alles gelöst.

Ende für heute :)

PS: Keine Angst, den Text kann ich sicher mehrfach verwenden :D
 
Zurück