• Hallo liebe Userinnen und User,

    nach bereits längeren Planungen und Vorbereitungen sind wir nun von vBulletin auf Xenforo umgestiegen. Die Umstellung musste leider aufgrund der Serverprobleme der letzten Tage notgedrungen vorverlegt werden. Das neue Forum ist soweit voll funktionsfähig, allerdings sind noch nicht alle der gewohnten Funktionen vorhanden. Nach Möglichkeit werden wir sie in den nächsten Wochen nachrüsten. Dafür sollte es nun einige der Probleme lösen, die wir in den letzten Tagen, Wochen und Monaten hatten. Auch der Server ist nun potenter als bei unserem alten Hoster, wodurch wir nun langfristig den Tank mit Bytes vollgetankt haben.

    Anfangs mag die neue Boardsoftware etwas ungewohnt sein, aber man findet sich recht schnell ein. Wir wissen, dass ihr alle Gewohnheitstiere seid, aber gebt dem neuen Board eine Chance.
    Sollte etwas der neuen oder auch gewohnten Funktionen unklar sein, könnt ihr den "Wo issn da der Button zu"-Thread im Feedback nutzen. Bugs meldet ihr bitte im Bugtracker, es wird sicher welche geben die uns noch nicht aufgefallen sind. Ich werde das dann versuchen, halbwegs im Startbeitrag übersichtlich zu halten, was an Arbeit noch aussteht.

    Neu ist, dass die Boardsoftware deutlich besser für Mobiltelefone und diverse Endgeräte geeignet ist und nun auch im mobilen Style alle Funktionen verfügbar sind. Am Desktop findet ihr oben rechts sowohl den Umschalter zwischen hellem und dunklem Style. Am Handy ist der Hell-/Dunkelschalter am Ende der Seite. Damit sollte zukünftig jeder sein Board so konfigurieren können, wie es ihm am liebsten ist.


    Die restlichen Funktionen sollten eigentlich soweit wie gewohnt funktionieren. Einfach mal ein wenig damit spielen oder bei Unklarheiten im Thread nachfragen. Viel Spaß im ngb 2.0.

C++ Pointerarray wie anlegen?

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.561
Ich bin ja mit C++ in den "Grundzügen" und habe mal eine Frage zu einem "Array" von Pointern und wie man für diese Speicher reserviert?

Das ganze soll logischerweise dynamisch geschehen soweit möglich. Aber ich weiß nicht genau wie oder ob ich "new" irgendwie einsetzen kann mit delete oder den Umweg über malloc/realloc gehen muß?

Folgende Klasse ist gegeben (Ausschnitt):

[src=cpp]class connection {
connection** connectionList; // Hat am Anfang "nullptr" als Wert bei Initialisierung
unsigned int connectionCount;
string name;

public:
connection();
~connection();
bool setName(string newName);
bool addConnection(string name);
};[/src]

Was hierbei das Problem macht, ich will auf "connection** connectionList" ja Instanzen hinzufügen, das geht auch mit "new connection();" aber wie kann ich dem Pointerarray Speicher zuweisen, damit ich über Indexe darauf zugreifen kann, also "connectionList[2] = new connection();" ?

Jetzt mache ich es eben so, aber durch das realloc sieht es ziemlich nach "C" aus, aber nicht C++.

[src=cpp]bool connection::addConnection(string newName) {
connectionList = (connection**) realloc(connectionList, sizeof(connection*) * (connectionCount + 1));
connectionList[connectionCount] = new connection();
connectionList[connectionCount]->setName(newName);
++connectionCount;
return true;
}[/src]

Wie kann ich das Pointerarray " connectionList** " erweitern, geht das mit "new" in irgendeiner Form oder ist das Keyword nur für Instanzen von Klassen?

Und vielleicht noch die Frage mit eingefügt - eine "delete connectionList" werde ich vermutlich nicht machen können wenn ich realloc nehme, oder? - Dann wegen realloc, ein "free()" anstelle dessen?
 

epiphora

aus Plastik
Veteran

Registriert
14 Juli 2013
Beiträge
3.894
Ort
DE-CIX
Nimm doch einen [kw]vector<connection*>[/kw] statt dem Array, damit bist Du viel flexibler:

[src=cpp]void connection::addConnection(string newName) {
connectionList.push_back(new connection())
}[/src]
 

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.561
  • Thread Starter Thread Starter
  • #3
Danke für den Tip, aber ich habe mich noch nicht umfangreich mit den Standarddatencontainern in C++ auseinandergesetzt und wollte es eigentlich erst mal die Arbeit mit Pointern, Klassen und dem Speichermanagement üben um selbst "Datencontainer" zu schreiben, eigentlich in etwa so, wie aus C gewohnt.

Aber klar, wenn die Klasse die Arbeit erledigt, wie ein "string" die char[] Hintergründe - finde ich das praktisch und sinnvoll, aber es klärt nicht wirklich die Frage(n)! ;)
 

Brother John

(schein)heilig
Veteran

Registriert
1 Aug. 2013
Beiträge
235
und wollte es eigentlich erst mal die Arbeit mit Pointern und dem Speichermanagement üben
Das könnte dann so aussehen:[src=cpp]class connection {
std::vector<std::unique_ptr<connection>> connectionList;
std::string name;

public:
// ...

bool addConnection(std::string name)
{
auto conn = std::make_unique<connection>();
conn.setName(name);
connectionList.push_back(std::move(conn));
return true;
}
};[/src]Hat Pointer und hat Speichermanagement, so wie man das in heutigem C++ macht. Zentraler Punkt dabei: Da gibt’s nirgendwo manuelles new/delete, weil’s das nicht braucht.

Vergiss am besten den Begriff »Speichermanagement«, der ist irreführend. C++ hat ein System für deterministisches, automatisches Ressourcenmanagement. Speicher ist nur eine davon. Dann gibt’s z.B. noch Dateihandles, Netzwerksockets, Datenbankverbindungen, Mutexe, … Alles was nach dem Pattern »holen – nutzen – zurückbringen« funktioniert. Und alles läuft über denselben Mechanismus.


um selbst "Datencontainer" zu schreiben, eigentlich in etwa so, wie aus C gewohnt.
Kannste machen, solange du die Container anschließend nicht produktiv nutzt. ;) Soll heißen, so etwas wie std::vector in einer Qualität zu schreiben wie in der Stdlib, ist hart! Wenn du aber tatsächlich zumindest in die Richtung eines selbst getippten new/delete kommen willst, dann ist eine naive Vector-Implementierung gar nicht so verkehrt. Stichwort dazu: RAII. Grundsätzlich kümmert sich der Ctor darum, Ressourcen – in dem Fall einen Speicherblock für die Elemente – zu allokieren; der Dtor gibt die Ressourcen wieder frei. Bei einem dynamischen Container kommt zwischendrin noch ein ähnliches freigeben/allokieren-Pärchen dazu, wenn der Container so weit wächst, dass der ursprüngliche Speicherblock nicht mehr ausreicht.

Wenn du keinen komplett unrealistischen Container bauen willst, wirst du auch um Templates nicht herumkommen. Denn schließlich braucht dein Container einen Speicherblock für x Elemente vom Typ … ja … das entscheidet dein Nutzer. Und damit bist du im Land der Templates.
 

BurnerR

Bot #0384479

Registriert
20 Juli 2013
Beiträge
5.504
Anknüpfend an Brother Johns Antwort will ich noch einen Vorteil benennen mit Smart-Pointer (hier die unique_ptr) und die STL-Container (hier: vector).
C++ hat den großen Vorteil, sehr ausdrucksstark zu sein. Die Verwendung von Smart-Pointern zeigt auch immer die Intention/Semantic an. Ein unique_ptr sagt (und erzwingt auch ein stückweit): Das Objekt, auf das da gezeigt wird, das gehört zu mir und nur zu mir. Entsprechend shared_ptr und weak_ptr. Das selbe für die Containerklassen. Vector entspricht von der Semantik noch am ehesten einem array, daneben gibt es noch set wo die Elemente einzigartig sind und sortiert werden und entsprechend unordered_set etc.
Beim draufgucken auf den Code/Datentyp siehst du also schon sehr viel von der Intention/Semantik. Ebenso so z.B. const-semantik.

C Programmierer kennen das im Prinzip auch, denn statische Typisierung ist ja auch nichts anderes, wenn man es mit Ducktyping vergleicht.
Statische Typisierung: Typ steht zur Kompilierzeit fest. Kompilerfehler bei einem Typkonflikt (der nicht implizit konvertiert werden kann/soll).
Ducktyping: Der Programmierer muss dafür sorgen, dass alle Objekte die Methoden/Attribute auch wirklich haben, die für sie aufgerufen werden.

Stell dir vor, du könntest in C sowas machen wie
[src=c]
int objekt;
objekt.doSomething();
[/src]
Und der Compiler meckert nicht, sondern das Programm kompiliert. Weil der Compiler dir die Verantwortung überlässt, dass die Variable "objekt" zu dem Zeitpunkt wo das Programm in Zeile 2 ist auch wirklich die Methode "doSomething" hat. - Setzt natürlich voraus, dass du der Variable auch Methoden hinzufügen kannst.
Man würde dann allerdings nicht mehr "int" zu anfangs schreiben, sondern eher einfach ganz allgemein
[src=c]
var objekt;
objekt.doSomething();
[/src]
Durch diese Abstraktion ist jetzt allerdings einiges an Semantik verloren gegangen, denn aus der Deklaration wird nicht mehr klar, was damit gemacht werden kann/soll/wird.

So ungefähr sehen raw-pointer aus, wenn man sich an smart-pointer gewöhnt hat, finde ich jedenfalls. Also das irgendwie die Intention hinter dem objekt / dem Zeiger fehlt.
 
Zuletzt bearbeitet:

Brother John

(schein)heilig
Veteran

Registriert
1 Aug. 2013
Beiträge
235
Eins fällt mir noch ein: Wenn du tatsächlich manuell mit Speicher hantierst, hast du drei Mechanismen zur Verfügung: malloc/calloc/realloc/free wie in C, und auf C++-Seite new/delete und new[]/delete[]. Du darfst die drei Mechnismen aber niemals mischen. Was mit einem davon allokiert wurde, muss mit genau dem auch wieder freigegeben werden.
 

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.561
  • Thread Starter Thread Starter
  • #7
Also okay, was "Handy" ist, wenn das Scope verlassen wird, wird auch automatisch der "Dtor" (Deconstructor?) aufgerufen, so fern vorhanden? So liest sich das RAII-Prinzip. :)

Heißt, ich brauche gar kein manuelles Aufräumen mehr machen? - Also mittels "ClearConnections" oder so etwas?
Weil automatisch der Deconstructor, auch für "Elemente" innerhalb eines Objekts angestoßen wird? Oder nur für das Objekt selbst? - Oder muß ich hier manuell eingreifen und diese Explizit für das jeweilige Object im Array aufrufen? (Vermutlich? Wenn ich diese mit "new" anlege in einem eine Attribut eines Objekts?)

Dann aber die Frage, ist der Einsatz von Realloc in diesem Beispiel falsch oder würde man es "genau so" machen, wenn man es nicht mit einer Standardklasse machen würde?
Vermutlich muß man dann auch das "free" benutzten wenn ich "malloc/realloc" verwende, wie in C.

Okay, also, wenn das mit so einem "vector" besser umgesetzt wäre, versuche ich das mal. Vermutlich ist Vector dann ein gepimptes "Array" im klassischen Sinne, nehme ich an.
Über die Details schaue ich mir einfach mal die Hilfe(n) an.

Aber nur um das mal zu sagen, das sollte eigentlich eine Übung für mich sein, und natürlich überrschneidet sich etwas mein C mit dem C++, wird noch spannend ob ich das ablegen bzw. kombinieren kann.

----

Eure Antworten kamen etwas später, aber ich stell die Fragen trotzdem mal ;)

@Burner
Super antwort. Das klärt schon einiges an den Fragen die ich gerade im Nachhinein habe.
Das eine Map ein über Schlüssel Wert - einzigartige "Schlüssel" Datenstruktur ist, habe ich auch schon gelesen, Vector klingt nur etwas komisch für ein Array, oder ich sehe das nicht in einem mathematischen Background.

In diesem Fall könnte es ein "unique_ptr" sein, aber später vielleicht auch "shared_ptr" (?), um "Knoten", wenn ich richtig liege nicht neu zu formen, wenn diese einmal erstellt sind... aber das geht wohl auch etwas weit, aber bevor ich alles auf "unique_ptr" setze *g

@BrotherJohn
Danke auch dir, das klärt auch die andere Frage über das Realloc/malloc... :) - Also "mit" free() ! ;)
 
Zuletzt bearbeitet:

Brother John

(schein)heilig
Veteran

Registriert
1 Aug. 2013
Beiträge
235
BurnerR schrieb:
Durch diese Abstraktion ist jetzt allerdings einiges an Semantik verloren gegangen, denn aus der Deklaration wird nicht mehr klar, was damit gemacht werden kann/soll/wird.

So ungefähr sehen raw-pointer aus, wenn man sich an smart-pointer gewöhnt hat, finde ich jedenfalls. Also das irgendwie die Intention hinter dem objekt / dem Zeiger fehlt.
Ganz so seh ichs nicht. Der Pointer ist ja typisiert und damit ist immernoch 100% klar, was du mit dem Objekt machen kannst. Was an Intention wirklich verloren geht, ist die Ownership-Information.

theSplit schrieb:
Heißt, ich brauche gar kein manuelles Aufräumen mehr machen? - Also mittels "ClearConnections" oder so etwas?
Prinzipiell meistens. Du musst aber ein bisschen aufpassen, was genau am Scope-Ende tatsächlich abgeräumt wird. Beispiel:
[src=cpp]class Foo {
public:
// ...
private:
std::vector<std::string> m_stringlist;
}

void bar() {
std::unique_ptr<Foo> funique = std::make_unique<Foo>();
Foo* fptr = new Foo;
Foo fobj;
}[/src]
Wenn bar() ihre schließende geschweifte Klammer erreicht, wird in umgekehrter Deklarationsreihenfolge abgeräumt und bei allen struct/class-Typen die Dtoren ausgeführt. D.h.:
  1. Der Dtor von fobj läuft. Dazu gehört auch implizit, dass die Dtors aller Member (und rekursiv derer Member etc.) laufen. Also werden erst alle String in m_stringlist gekillt, dann der Vector, dann fobj selbst.
  2. Der Dtor von fptr … äh … existiert nicht. Das ist ein Pointer, ein fundamentaler Typ. Der hat keinen Dtor. Auf welchen Typ dieser Pointer zeigt, ist egal. Out-of-scope geht der Pointer selbst. Also: autsch! Das Foo-Objekt hinter dem Pointer lebt weiter und wir haben ein Speicherleck.
  3. Der Dtor von funique läuft. Der existiert, weil’s mit unique_ptr einer der Smartpointer ist. Technisch ist der in allen Stdlibs, die ich kenne, als struct implementiert. funique zeigt genauso wie fptr auf ein Foo-Objekt irgendwo anders im Speicher. Aber der funique-Dtor stellt sicher, dass dieses Objekt jetzt mit zerstört wird.
Als Daumenregel: Wenn du eine neue Klasse schreibst und erwischst dich dabei, einen Dtor zu tippen, lohnt es sich oft, über das Design nochmal nachzudenken. Fast immer geht das nämlich auch ohne. Ausnahme: Wenn du einen RAII-Ressourcenmanager schreibst, z.B. einen »Smartpointer« für eine Datenbankverbindung. Dann ist’s ja gerade Sinn der Sache, dass du im Ctor ausdrücklich die Verbindung aufbaust und im Dtor wieder killst.

Dann aber die Frage, ist der Einsatz von Realloc in diesem Beispiel falsch
Falsch direkt nicht, aber unschön. Der malloc/free-Mechanismus existiert für die C-Kompatibilität. Außerhalb davon hat er eigentlich nichts verloren.

Vermutlich ist Vector dann ein gepimptes "Array" im klassischen Sinne, nehme ich an.
Gepimpt, ha! :) Schon irgendwie. Der große Unterschied zum Array ist, dass er zur Laufzeit seine Größe ändern kann.

Btw: Für das klassische Array mit fester Größe hat C++ std::array. Kann das, was ein C-Array kann, aber kennt seine eigene Länge, konvertiert nicht bei erster Gelegenheit zum Pointer, und die Deklarationssyntax ist weniger gaga. Macht insgesamt deutlich mehr Spaß als das C-Array.

In diesem Fall könnte es ein "unique_ptr" sein, aber später vielleicht auch "shared_ptr" (?), um "Knoten", wenn ich richtig liege nicht neu zu formen, wenn diese einmal erstellt sind...
Ich bring’s mal kurz auf den Punkt: Bei den Smartpointern geht es immer um Ownership, also darum, wer fürs Aufräumen verantwortlich ist. Der unique_ptr ist Single-Ownership: Das Objekt gehört mir allein. Wenn ich sterbe, dann muss ich das Ding mit aufräumen. Shared_ptr ist Multi-Ownership: Viele teilen sich das Objekt und zerstören muss es der letzte, der es noch in der Hand hat.

I.d.R. sollte Ownership eindeutig sein. D.h. unique_ptr ist der normale Wald-und-Wiesen-Smartpointer und shared_ptr das Spezialtool für die außergewöhnlicheren Fälle.
 

BurnerR

Bot #0384479

Registriert
20 Juli 2013
Beiträge
5.504
Ganz so seh ichs nicht. Der Pointer ist ja typisiert und damit ist immernoch 100% klar, was du mit dem Objekt machen kannst. Was an Intention wirklich verloren geht, ist die Ownership-Information.
Hast recht!
 

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.561
  • Thread Starter Thread Starter
  • #10
Was für eine Lektüre am Morgen :cool:

Also mit dem Beispiel wie sich eine Funktion "rückwärts aufräumt" bzw. der Stack wieder abgearbeitet wird mit Aufruf der ~Dtor, macht das ganze ziemlich plausibel.
Den Dtor hatte ich in der Tat dafür aber angelegt, also schon grundsätzlich richtig eingesetzt, um das "Pointerarray" zu löschen, allerdings gut zu wissen, das man dies nicht einmal explizit aufrufen muß , wenn man mit "unique_ptr" arbeitet.

Allerdings ist mir dieses ganze RAII Prinzip so nicht klar gewesen bzw. wie genau das mit einspielt mit dem "Wissen" im Gepäck sollte es schon viel leichter sein, Code zu schreiben, der nicht nur funktioniert aber auch "ordentlich" die Sprachfeatures nutzt. Ist allerdings ein wenig gewöhnungsbedürftig sich darauf "zu verlassen". Wenn man in C Speicher anfordert oder Strukturen anlegt, muß(!) man den explizit wieder aufräumen/freigeben.
Ich will nur dazu sagen, im Grunde ist das kein großer Schmerz das zu tun in C, wenn man sich daran gewöhnt zu jeder Struktur und jeder Allocation gleich die passende "Cleanup" Routine bzw. Befehl zu schreiben. Auch wenn es mit Pointern von Pointer von Pointer...... kompliziert "anmuten" kann.

Ansonsten eine ziemlich gute Erklärung BrotherJohn, man würde sich manchmal wünschen, andere Dinge im Leben wären so "logisch" und erklärbar. :T

Das mit dem "shared_ptr" wirft allerdings eine Frage auf, wenn ich ein Objekt so deklariere, damit also - sagen wir ich gehe die bewusste Entscheidung ein (damit das Ownership klar zu machen(?) und teile das Objekt/die Daten mit anderen Objekten zum Beispiel über Adressreferenzen(?)innerhalb der anderen Objekte. Wie kann man überprüfen, ob das Objekt noch referenziert ist? - Gibt es da eine Mechanik für, so ne Art "Ref(erenz)Count" oder ähnliches?
Oder sind solche Details mir selbst überlassen, das ich selbst festlege ob ich das Feature "RefCount" - brauche, und das dann in der Klasse/Struktur vereinbare und selbst das Housekeeping betreibe wann das Objekt dann "freigegeben" werden darf wenn keine Referenzen mehr existieren?

Gut, die Frage erübrigt sich vielleicht, wenn ich ein Array habe, das nur wächst, und dessen Inhalt sich durch andere geteilt wird - und ist die Anwendung zu beenden, räume ich explizit auf mittels Dtor-Aufruf des Objekts, weil "shared_ptr". Bzw. mache die Objekte gleich als Unikat mit "unqiute_ptr" und lasse C++ die Arbeit verrichten.

Vielleicht sollte ich mir auch erst einmal nicht so viele Gedanken über das "shared" machen, auch wenn es von der Idee her "besser" klingt, als etwas "unique" zu machen, speziell wenn Objekte/Werte mit anderen Objekten geteilt werden. Rein vom der genannten "Ownership-Information" würde der erste Fall aber mehr Sinn für mich machen....

Aber gut, erst mal kleine Brötchen backen, "pre mature optimization is the root of all evil!" ;)

Edit:

So, hab mal hier aufgeräumt.... ausversehen :o

Daher nur kurz, ich arbeite jetzt mit einem Unique_ptr für die Hauptklasse (eigentlich fast nur ein Datencontainer) mit einigen Methoden, die damit die anderen Instanzen verwaltet und diese dann auch beim delete druch den unique_ptr aufräumt.
Auch verwende ich nun einen Vector <connections*> - der die Adresse der "cross-gelinkten" Objekte beinhaltet. Es fehlt legidlich ein entfernen der Elemente aus der Liste bzw. auch das entfernen von Verknüpfungen innerhalb der Instanzen.

Hat mich 4 Stunden gekostet das alles so hinzubekommen, wegen des ganzen Typecasting und * versus & oder hier mal ein .get() oder ich hatte Memory Leaks nach dem "push_back" - obwohl das Programm korrekt lief....

[src=cpp]#include <iostream>
#include <vector>
#include <memory>

using namespace std;

//-------------------------------------------------------------------


class connection {
private:
string name;
vector<connection*> connections;

public:
connection() {
name = "Not set";
}

string getName() {
return name;
}

bool setName(string newName) {
name = newName;
return true;
}

vector<connection*> getConnections() {
return connections;
}

bool addNodeLink(unique_ptr<connection>* nodeToConnect) {
if (!connections.empty()) {
for (connection* node : connections) {
if (node == (connection*) nodeToConnect->get()) {
return false;
}
}
}

connections.push_back(nodeToConnect->get());
return true;
}
};

//-------------------------------------------------------------------


class connectionList_ {
private:
unsigned int connectionCount;
vector<unique_ptr<connection>> connectionList;

public:
connectionList_() {
connectionCount = 0;
}

bool addConnection(string name) {
for (unique_ptr<connection> &conn : connectionList) {
if (conn->getName() == name) {
cout << "Node \"" << name << "\" already present in connection list.";
return false;
}
}
unique_ptr<connection> conn = make_unique<connection>();
conn->setName(name);



connectionList.push_back(move(conn));

cout << "Added connection \"" << name << "\" to connection list\n";

++connectionCount;
return true;
}

void listConnections(string name) {
if (connectionList.empty()) {
cout << "No objects in connection list.\n";
return;
}


for (unique_ptr<connection> &node:connectionList) {
if (node->getName() == name) {
cout << "\nLinks of \"" << name << "\" @ " << node.get() << endl;
for (connection* link:node->getConnections()) {
cout << link->getName() << " @ " << link << endl;
}

return;
}
}

cout << "\"" << name << "\" is not present in connection list.\n";
}

bool linkConnections(string src, string dest) {
unique_ptr<connection>* source = nullptr;
unique_ptr<connection>* destination = nullptr;

for (unique_ptr<connection> &node : connectionList) {
if (node->getName() == src) {
source = &node;
break;
}
}

if (source == nullptr) {
cout << "Item \"" << src << "\" not present for linking.\n";
return false;
}

for (unique_ptr<connection> &node : connectionList) {
if (node->getName() == dest) {
destination = &node;
break;
}
}

if (destination == nullptr) {
cout << "Item \"" << dest << "\" not present for linking.\n";
return false;
}

if ( (*source)->addNodeLink(destination) && (*destination)->addNodeLink(source)) {
cout << "Added links from \"" << (*source)->getName() << "\" to \"" << (*destination)->getName() << "\"\n";
} else {
cout << "Link present, not adding: \"" << (*source)->getName() << "\" to \"" << (*destination)->getName() << "\"\n";
}



return true;
}

};

//-------------------------------------------------------------------

int main() {
connectionList_* connectionList = new connectionList_();
connectionList->listConnections("Object 4");

connectionList->addConnection("Object 1");
connectionList->addConnection("Object 2");
connectionList->listConnections("Object 4");
connectionList->addConnection("Object 3");
connectionList->addConnection("Object 4");

connectionList->linkConnections("Object 1", "Object 3");
connectionList->linkConnections("Object 1", "Object 3");
connectionList->linkConnections("Object 3", "Object 4");
connectionList->linkConnections("Object 2", "Object 4");
connectionList->linkConnections("Object 2", "Object 3");
connectionList->linkConnections("Object 5", "Object 3");

connectionList->listConnections("Object 1");
connectionList->listConnections("Object 2");
connectionList->listConnections("Object 3");
connectionList->listConnections("Object 4");

delete connectionList;
return 0;
}
[/src]
 
Zuletzt bearbeitet:

Brother John

(schein)heilig
Veteran

Registriert
1 Aug. 2013
Beiträge
235
Eigentlich wollte ich ja nur kurz drüberblättern. Aber irgendwie ist mir dann die Tastatur ausgerutscht. :D

[src=cpp]using namespace std;[/src]
Why is “using namespace std” considered bad practice?

[src=cpp]vector<connection*> getConnections() {
return connections;
}

// --- Gegenvorschlag ---
const vector<connection*>& getConnections() {
return connections;
}

// Und Beispielverwendung:
c.getConnections().push_back(...);
[/src]
Aus zwei Gründen:

a) Deine Version compiliert mit dem Beispiel, hat aber keinen Effekt, weil der Membervector kopiert, dann ein Element angehängt und dann der kopierte Vector zerstört wird. Ist wahrscheinlich nicht die Intention gewesen. Der Gegenvorschlag compiliert nicht (wegen dem const).

b) Deine Variante kopiert den Membervector immer, auch bei einem simplen Lesezugriff, wo das gar nicht notwendig wäre. const& sorgt dafür, dass beim Lesen nicht kopiert wird und schreiben nicht möglich ist.

[src=cpp]bool addNodeLink(unique_ptr<connection>* nodeToConnect) {
if (!connections.empty()) {
for (connection* node : connections) {
if (node == (connection*) nodeToConnect->get()) {
return false;
}
}
}
...
}[/src]
Ein Pointer auf einen unique_ptr ist praktisch nie sinnvoll. Und Raw-Pointer sind auch nicht grundsätzlich böse. addNodeLink hat mit der Ownership von nodeToConnect nichts zu tun. Dann ist es auch egal, ob die Node per unique_ptr oder sonstwie verwaltet wird. Solange sichergestellt ist, dass die Node solange lebt, wie die Funktion läuft, ist es vollkommen normal und gut, sie als
const nodeToConnect&
oder
const nodeToConnect*
zu übergeben. (Oder auch ohne const, wenn Schreibzugriff nötig ist.) Ob Pointer oder Referenz, daraus kann man dann wieder einen Glaubenskrieg machen.

Zum folgenden if und der for-Schleife: Das if kannst du dir sparen. Wenn connections leer ist, läuft die Schleife nicht.

Der C-style Cast – also das (connection*) – ist ein Fall von einer vollkommen normalen C-Konstruktion, die man in C++ vermeiden sollte. Warum C-style Casts gefährlich sind. Davon abgesehen und ohne es compiliert zu haben: Ist der Cast nicht redundant? nodeToConnect->get() liefert doch schon einen connection*.

Und um als Hinweis am Rande die Algorithem-Library anzusprechen, weil die viel zu häufig ignoriert wird (das kenn ich von mir selbst :)). Die Schleife könnte man durch ein std::find() ersetzen.

[src=cpp]class connectionList_ {
private:
unsigned int connectionCount;
vector<unique_ptr<connection>> connectionList;
[/src]
Warum gibts denn den connectionCount? Ich sehe gerade nicht, wo sich der von connectionList.size() unterscheidet.

[src=cpp]
bool addConnection(string name) {
for (unique_ptr<connection> &conn : connectionList) {
if (conn->getName() == name) {
cout << "Node \"" << name << "\" already present in connection list.";
return false;
}
}
unique_ptr<connection> conn = make_unique<connection>();
conn->setName(name);
connectionList.push_back(move(conn));
...[/src]
Vorschlag zur Bequemlichkeit. Wenn du der connection-Klasse einen Ctor
connection(const std::string& name);
verpasst, dann kannst du einfach schreiben:
connectionList.push_back(std::make_unique<connection>(name));

[src=cpp] bool linkConnections(string src, string dest) {
unique_ptr<connection>* source = nullptr;
unique_ptr<connection>* destination = nullptr;[/src]
Ich musste genau lesen, aber ich bin mir ziemlich sicher, dass die Funktion das richtige tut. Würde man aber eher nicht auf die Art lösen. Deswegen halte ich mal als Gedankenfutter eine C++igere, weil Iterator-basierte, Variante dagegen.
[src=cpp]bool linkConnections(const std::string& srcName, const std::string& destName) {
// std::find_if + Lambda anstatt std::find, weil die Namen
// verglichen werden müssen und nicht die connection-Objekte
// direkt.
const auto src_iter = std::find_if(
connectionList.begin(), connectionList.end(),
[&] (const unique_ptr<connection>& c) {
return c.getName() == srcName;
});

if (src_iter == connectionList.end()) {
// Fehlerbehandlung...
return false;
}

// Jaja, gedoppelter Code. In produktiv würde ich dass
// wahrsch. in ne Helfer-Fkt. auslagern.
const auto dest_iter = std::find_if(
connectionList.begin(), connectionList.end(),
[&] (const unique_ptr<connection>& c) {
return c.getName() == destName;
});

if (src_iter == connectionList.end()) {
// Fehlerbehandlung...
return false;
}

// Angenommen es gäbe:
// bool connection::addNodeLink(const connection*);
if (src_iter->addNodeLink(dest_iter->get())
&& dest_iter->addNodeLink(src_iter->get()))
{
// Added links ...
}
else {
// Link present ...
}

return true;
}[/src]

Auch wenn das jetzt viel Gemecker war: Insgesamt sieht das doch recht ordentlich aus. Ownership sieht sauber aus und du hast auch der Versuchung widerstanden, das Typsystem zu vergewaltigen (mit void* oder bösen Casts). Das sind imo die zwei richtig dicken Themen, wo sich C und C++ fundamental unterscheiden. Und das passt.
 

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.561
  • Thread Starter Thread Starter
  • #12
@Brother John: Vielen lieben Dank für das ausführliche Feedback.

Also mit dem Namespace - okay, darauf muss man erst mal kommen... also wenn ich eine Methode schreibe, die im Standardnamespace auftauchen kann, kollidiert es spätestens dann wenn ich richtig gelesen habe.

Die Erklärung zum "const" Keyword ist auch sehr spannend, mal schauen wie ich das umsetzen kann oder ob ich etwas ändern muß, das probiere ich noch einmal aus.

Zum Typecasting - die Funktionen wollten den Pointer nicht annehmen bzw. hat QtCreator gemeckert/meckern lass, dass der Typ den ich übergebe vom "Typ" 'unique_ptr<Klasse(*)>' ist.
Deßhalb auch mal der "C" cast von unique_ptr auf einen normalen (connection*) Pointer.

Aber auch hier, ich versuche mal wie ich das raus bekomme ohne das der Compiler (/QtCreator) meckert. Aber kann gut sein das ich da nochmal nachfragen muß.

Das man eine Instanz bei der Initialisierung gleich benennt, scheint mir auch der richtige Weg zu sein.... warum hab ich das bloß nicht gemacht?
Vermutlich weil ich vorher einen ganzen anderen Use Case hatte und die Instanzen innerhalb der Objekte erzeugt/gespeichert habe (was wenig Sinn gemacht hat).

Zu:
[src=cpp]bool linkConnections(string src, string dest) {
unique_ptr<connection>* source = nullptr;
unique_ptr<connection>* destination = nullptr;[/src]

Der Sinn dahinter ist, im ersten Durchlauf ermitteln wir die Adresse der Quelle, über den Namensvergleich, im zweiten die des Ziels.

Ich sehe aber glaube ich ein Problem bei deinem "if" zu dem "find_if" - die Überprüfung müsste doch logischweise die sein, ob ' "src_iter" == nullptr ' ist, oder eben nicht, oder? Wenn ich richtig lese das find_if ja irgendwas rausgeben muß, wenn es nichts findet?

Warum darunter dann die Überprüfung ob der "src_iter == connectionList.end()" (?) das Listenende ist?
Selbst wenn es das Listenende ist, rein zur Fehlerbehandlung, kann das Ziel trotzdem gültig sein. In dem Code kann ja "object 1" wie auch mit "object 4" (am Listenende befindlich) verknüpft werden, daher wäre das gar kein Fehler...

Noch kurz zum "Typecasting" : den ganzen Artikel zu lese, ist ja verdammt viel Materie... das muß ich mir mal aufheben wenn ich etwas sicherer in dem Thema bin.
Jetzt mutet das noch ein wenig Komplex bzw. Fremd an.

Und danke für das Lob, ich geb mir Mühe ;)


PS: Zum Keyword "auto", ich sehe den Sinn dahinter, aber da ich gerade erst lerne damit zu arbeiten, nehme ich kein "auto" sonder deklariere genau was für ein Datentyp bzw. welcher Datentyp eine Variable/Pointer/Iterator.... ist. Wenn ich das nicht lerne, verstehe ich die Hintergründe nicht ;)

Edit: Ja, zum "connectionCount" - das ist ein Überbleibsel, hab gedacht ich brauche das, aber wenn ".size()" auch geht - nehme ich die Methode.
 
Zuletzt bearbeitet:

BurnerR

Bot #0384479

Registriert
20 Juli 2013
Beiträge
5.504
Wie kann man überprüfen, ob das Objekt noch referenziert ist? - Gibt es da eine Mechanik für, so ne Art "Ref(erenz)Count" oder ähnliches?
Ein shared pointer ist eigentlich nicht viel mehr als ein pointer mit einem refcount, von daher, ja die Mechanik gibt es ;).
Allerdings, wenn der refcount gleich null ist, dann kannst du das Objekt ja sowieso nicht mehr ansprechen und der Speicher ist dann schon freigegeben worden.

die Funktionen wollten den Pointer nicht annehmen bzw. hat QtCreator gemeckert/meckern lass, dass der Typ den ich übergebe vom "Typ" 'unique_ptr<Klasse(*)>' ist.
Das Ende klingt etwas unheimlich, du würdest ja nicht hingehen und etwas vom Typ "unique_ptr<Klasse*>" deklarieren, da das vermutlich in einem leak endet. Der smart pointer gibt ja nur den Speichern für den raw pointer frei am Ende, nicht aber den, worauf dieser zeigt.

Noch kurz zum "Typecasting" : den ganzen Artikel zu lese, ist ja verdammt viel Materie... das muß ich mir mal aufheben wenn ich etwas sicherer in dem Thema bin.
Der TL;DR: Don't do it :P.

PS: Zum Keyword "auto", ich sehe den Sinn dahinter, aber da ich gerade erst lerne damit zu arbeiten, nehme ich kein "auto" sonder deklariere genau was für ein Datentyp bzw. welcher Datentyp eine Variable/Pointer/Iterator.... ist. Wenn ich das nicht lerne, verstehe ich die Hintergründe nicht
Vielleicht für später: Ich verwende auto vor allem, wenn mir der konkrete/genaue Typ egal ist, also insbesondere bei allen iteratoren, die ja recht unübersichtliche Typen haben. Vielleicht kann ja Brother John noch mehr insights dazu geben, der ist ja nochmal fitter als ich.


Du lernst ja richtig C++ programmieren hier, also nicht nur so "hauptsache läuft" :D.
 

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.561
  • Thread Starter Thread Starter
  • #14
@Burnerr, zu deinem Einwand bezüglich des Pointers, es sollte vermutlich eher heißen "unique_ptr<klasse>*" wenn die Adresse dieses SmartPointer übergeben wurde - aber wie ich gerade sehe, verliert der Pointer seine "smarten" "Vor"typen, wenn man "get()" abruft, kann das sein?

Weil wenn ich anstelle dessen zum Beispiel nehme "&(*src_iter)" (also um das Objekt anzusprechen und die Adresse zu bekommen, ist es immer noch ein "unique_ptr<connection>*" nehme ich das "src_iter->get()" erhalte ich gleich die korrekte Addresse und muß es auch nicht zu einem "connection*" casten.

Also so 100% steige ich da noch nicht genau durch was da der genaue Unterschied ist zwischen (*item) und item->get() .

---

Ich muss auch dazu sagen, die Lambdas für "find_if" sehen auch sehr spannend aus, da würde ich lieber das Beispiel in einer anderen Funktion/Methode der Klasse(?) verwenden:

Wie hier gezeigt wird mit ein vector<int>:
http://www.cplusplus.com/reference/algorithm/find_if/

Also was das Lambda macht verstehe ich in etwa, aber die Syntax ist eine harte Nuss! ;)

Achso und noch eine Kleinigkeit zum "const":
Ich lasse den Vector<connection*> jetzt per Adresse übergeben an die Funtionen die es benötigen, weil ich das "const" nicht direkt verwenden kann, sollte ja ressourcenschonender sein. Und es gibt generellen Schreibzugriff.

[src=cpp]vector<connection*>* getConnections() {
return &connections;
}[/src]

Und im Ctor einer connection wird jetz auch der Name gleich mit als parameter übertragen und direkt festgelegt.


Und hier mal der aktualisierte Quelltext, ich habe dort die Änderungen eingepflegt und auch sind noch removeConnection und removeLink dazu gekommen.... beide relativ identisch, bis auf den Unterschied das removeConnection per Adresse überprüft, währen removeLink mit "strings" arbeitet.


[src=cpp]#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>

using namespace std;

//-------------------------------------------------------------------


class connection {
private:
string name;
vector<connection*> connections;

public:
connection(string newName) {
name = newName;
}

string getName() {
return name;
}

vector<connection*>* getConnections() {
return &connections;
}

bool addNodeLink(connection* nodeToConnect) {
for (connection* &node : connections) {
if (node == nodeToConnect) {
return false;
}
}

connections.push_back(nodeToConnect);
return true;
}
};

//-------------------------------------------------------------------


class connectionList_ {
private:
vector<unique_ptr<connection>> connectionList;

public:
connectionList_(){

};

bool addConnection(string name) {
for (unique_ptr<connection> &conn : connectionList) {
if (conn->getName() == name) {
cout << "Node \"" << name << "\" already present in connection list.";
return false;
}
}

/*
unique_ptr<connection> conn = make_unique<connection>();
conn->setName(name);
connectionList.push_back(move(conn));
*/

connectionList.push_back(make_unique<connection>(name));

cout << "Added connection \"" << name << "\" to connection list\n";

return true;
}

bool removeConnection(string targetName) {
vector<connection*>* sourceConnections;
vector<connection*>* targetConnections;

for (vector<unique_ptr<connection>>::iterator it = connectionList.begin(); it != connectionList.end(); ++it) {

if ((*it)->getName() == targetName) {
sourceConnections = (*it)->getConnections();

for (vector<connection*>::iterator itSource = (*sourceConnections).begin(); itSource != (*sourceConnections).end(); ++itSource) {
targetConnections = (*itSource)->getConnections();

for (vector<connection*>::iterator itTarget = (*targetConnections).begin(); itTarget != (*targetConnections).end(); ++itTarget) {
if (&(**it) == &(**itTarget)) {
(*sourceConnections).erase(itSource);
(*targetConnections).erase(itTarget);
--itSource;
--itTarget;
}
}
}

connectionList.erase(it);
cout << "Erased connection \"" << targetName << "\".\n";

return true;
}
}

cout << "Could not erase connection \"" << targetName << "\", item not found in connection list.\n";
return false;
}

bool removeLink(string srcName, string targetName) {
vector<connection*>* sourceConnections;
vector<connection*>* targetConnections;

for (vector<unique_ptr<connection>>::const_iterator it = connectionList.begin(); it != connectionList.end(); ++it) {
if ((*it)->getName() == srcName) {
sourceConnections = (*it)->getConnections();

for (vector<connection*>::const_iterator itSource = (*sourceConnections).begin(); itSource != (*sourceConnections).end(); ++itSource) {
if ((*itSource)->getName() == targetName) {
targetConnections = (*itSource)->getConnections();

for (vector<connection*>::const_iterator itTarget = (*targetConnections).begin(); itTarget != (*targetConnections).end(); ++itTarget) {
if (targetName == (*itSource)->getName()) {
(*sourceConnections).erase(itSource);
(*targetConnections).erase(itTarget);
cout << "Removed link \"" << targetName << "\" of \"" << srcName << "\".\n";
return true;
}
}
}
}

cout << "Remove link failed, target \"" << targetName << "\" not found in source \"" << srcName << "\".\n";
return false;
}
}

cout << "Remove link failed, source \"" << srcName << "\" not present.\n";
return false;
}

void listConnections(string name) {
if (connectionList.empty()) {
cout << "No objects in connection list.\n";
return;
}


for (unique_ptr<connection> &node:connectionList) {
if (node->getName() == name) {
cout << "\nLinks of \"" << name << "\" @ " << node.get() << endl;
for (connection* link:(*node->getConnections())) {
cout << link->getName() << " @ " << link << endl;
}

return;
}
}

cout << "\"" << name << "\" is not present in connection list.\n";
}

bool linkConnections(string src, string dest) {
bool hasSource = false;
bool hasDestination = false;

const auto src_iter = find_if(
connectionList.begin(), connectionList.end(),
[&] (const unique_ptr<connection>& conn) {
if (conn->getName() == src) {
hasSource = true;
return true;
}

return false;
});

if (!hasSource) {
cout << "Item \"" << src << "\" not present for linking.\n";
return false;
}

const auto dest_iter = find_if(
connectionList.begin(), connectionList.end(),
[&] (const unique_ptr<connection>& conn) {
if (conn->getName() == dest) {
hasDestination = true;
return true;
}

return false;
});

if (!hasDestination) {
cout << "Item \"" << dest << "\" not present for linking.\n";
return false;
}

if ( (*src_iter)->addNodeLink(dest_iter->get()) && (*dest_iter)->addNodeLink(src_iter->get()) ) {
cout << "Added links from \"" << (*src_iter)->getName() << "\" to \"" << (*dest_iter)->getName() << "\"\n";
} else {
cout << "Link present, not adding: \"" << (*src_iter)->getName() << "\" to \"" << (*dest_iter)->getName() << "\"\n";
}

return true;
}

};

//-------------------------------------------------------------------

int main() {
connectionList_* connectionList = new connectionList_();
connectionList->listConnections("Object 4");

connectionList->addConnection("Object 1");
connectionList->addConnection("Object 2");
connectionList->listConnections("Object 4");
connectionList->addConnection("Object 3");
connectionList->addConnection("Object 4");

connectionList->linkConnections("Object 1", "Object 3");
connectionList->linkConnections("Object 1", "Object 3");
connectionList->linkConnections("Object 3", "Object 4");
connectionList->linkConnections("Object 2", "Object 4");
connectionList->linkConnections("Object 2", "Object 3");
connectionList->linkConnections("Object 5", "Object 3");

connectionList->listConnections("Object 1");
connectionList->listConnections("Object 2");
connectionList->listConnections("Object 3");
connectionList->listConnections("Object 4");


connectionList->listConnections("Object 2");
connectionList->removeLink("Object 2", "Object 3");
connectionList->listConnections("Object 2");

connectionList->removeConnection("Object 4");
connectionList->listConnections("Object 4");
connectionList->listConnections("Object 2");

delete connectionList;
return 0;
}

[/src]
 
Zuletzt bearbeitet:

Brother John

(schein)heilig
Veteran

Registriert
1 Aug. 2013
Beiträge
235
Lambdas, hihi. Dazu gibts den schönen Schnipsel:
Code:
[](){}();
Ist das gültiges C++? Und wenn ja, was genau ist es und was tut es? Viel Spaß. :D

Zum unique_ptr<T>*: Just don’t do it. :) Wenn’s jetzt so ein Beispiel ist wie bei dem Algo in linkConnections(), wozu dient die mitgeschleifte Ownership-Info aus zweiter Hand? Mit der Modernes-C++-Brille auf ist es vollkommen selbstverständlich, dass ein Raw-Pointer non-owning ist; genau das drückt ein unique_ptr<T>* aus, nur mit viel Barock außen herum. Den entspr. Raw-Pointer zu kriegen ist trivial: unique_ptr<T>::get(). Aus dem Grund kann und soll jeder unique_ptr<T>* eigentlich ein T* sein – fast immer, weil irgend eine sinnvolle Spezialfallausnahme gibt es immer. Aber nicht in diesem Thread.

theSplit schrieb:
Ich sehe aber glaube ich ein Problem bei deinem "if" zu dem "find_if" - die Überprüfung müsste doch logischweise die sein, ob ' "src_iter" == nullptr ' ist, oder eben nicht, oder?
Nein, kein Problem. Ein Iterator ist kein Pointer. Er hat nur eine API, die stark an die des Pointers angelehnt ist. Der Marker fürs Ende der Sequenz und genauso der Nicht-Gefunden-Marker ist der End-Iterator. D.h. das src_iter == connectionList.end() ist die Prüfung auf »nicht gefunden«.

Wichtig dabei: C++-Iteratoren verhalten sich »pointerähnlich« und zeigen auf ein Element – im Gegensatz zu Java-Iteratoren. Die zeigen zwischen Elemente. Für end() ergibt sich die blöde Situation, dass der nichts hat, worauf er zeigen kann. Deswegen zeigt der konzeptionell auf past-the-last-element. Praktisch geht das natürlich nicht. Deswegen ist end() ein reiner Marker. Man darf ihn nicht dereferenzieren. Illustations auf cppreference.com dazu.

Damit kann auch aus dem neuen linkConnections() das hasSource/hasDestination wieder raus. Denn die Konstruktion tut exakt dasselbe wie der Test auf end().

theSplit schrieb:
Also so 100% steige ich da noch nicht genau durch was da der genaue Unterschied ist zwischen (*item) und item->get() .
*item liefert dir eine Referenz auf das gemanagte Objekt.
item.get() liefert dir einen Pointer auf das gemanagte Objekt.
item->get() hat nichts mit dem Smartpointer zu tun. So rufst du die Memberfunktion get() des gemanagten Objekts auf.
Lass mich nochmal auf http://cppreference.com hinweisen. Imo die beste C++-Referenz im Netz. Da kannst du solche Sachen sehr gut nachschauen.

theSplit schrieb:
vector<connection*>* getConnections()
Bin ich kein Fan von.
Dicker Punkt: Damit hast du den Member-Vector effektiv zur public Variable gemacht. Wenn du es der Welt erlaubst, direkt in deinen Magen reinzugrabschen, sollte das einen echt guten Grund haben. Den seh ich hier nicht. Es sollte besser die connection-Klasse genau die Zugriffsfunktionen anbieten, die es wirklich braucht.
Kleinerer Punkt: Mein Sinn für C++-Ästhetik will da eine & zurückgeben. Das ist ein gutes Stück Bauchgefühl und die bei Pointern übliche Frage: Kann da nullptr zurückkommen? Das Problem hast du mit einer & nicht.

BurnerR schrieb:
Ich verwende auto vor allem, wenn mir der konkrete/genaue Typ egal ist, also insbesondere bei allen iteratoren, die ja recht unübersichtliche Typen haben.
Der Ansatz scheint sich so langsam als Best Practice durchzusetzen. Genauso mach ich’s auch. Und Iteratoren sind das Paradebeispiel. Sowas wie
Code:
std::unordered_map<boost::uuids::uuid, std::vector<std::string>>::const_iterator
will doch keiner ausschreiben. Da ist ja die Zeile allein mit dem Typnamen voll.

auto zu übertreiben kann aber auch ins Auge gehen. Es gibt schon in der normalen Verwendung Stolperfallen, v.a. in range-for ähnlichen Konstruktionen. Denn auto nimmt weder const noch & oder * mit. D.h.

[src=cpp]std::vector<connection> clist;
// ...

// Kopiert jedes connection-Objekt aus der Liste heraus. Die Kopie ist schreibbar.
for (auto c : clist) {}

// Kopiert jedes connection-Objekt aus der Liste heraus. Die Kopie ist readonly.
for (const auto c : clist) {}

// Schreibbare Ref auf die Elemente
for (auto& c : clist) {}

// Readonly Ref auf die Elemente
for (const auto& c : clist) {}[/src]

Alles in allem macht es deswegen Sinn, erstmal ohne zu überlegen zu tippen:
[src=cpp]for (const auto& elem : sequence) { ... }[/src]
Damit kann man nichts falsch machen. Und wenns Schreibzugriff braucht, merkt das spätestens der Compiler.
 

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.561
  • Thread Starter Thread Starter
  • #16
Viel Feedback, ich muss das mal Sacken lassen ;)

Auf ein Bier in der Bar ;)
 

BurnerR

Bot #0384479

Registriert
20 Juli 2013
Beiträge
5.504
Ich weiß noch, wie begeistert ich war, als ich zum ersten mal

for ( auto c : clist) {}
gesehen habe, C++ war für mich damit im neuen Jahrtausend angekommen ohne den ganzen superfiesen syntaktischen/kognitiven Ballast und auch

for (const auto& c : clist) {}
Ist ja eine großartige Symbiose aus Kürze und Ausdrucksstärke und damit super schön und klar.
 

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.561
  • Thread Starter Thread Starter
  • #18
Guten Morgen, ich glaube jetzt beim noch einmal lesen, ist es etwas klarer für mich...

Also zeigt Iteratoror.end() (auch ans Diagramm angelehnt) - das wir alle Elemente durchschritten und "dahinter" angekommen sind.... das ist etwas merkwürdig und finde ich ich nicht direkt intuitiv, wenn begin() auf das erste Element "zeigt".

Rein von der Anwendung macht der Fall dann aber schon Sinn, da ja dann damit erkennbar ist, ob wirklich alles "durchlaufen" ist - nur finde ich den Namen dann etwas verwirrend - speziell das end() ist leere zeigt, muß man wissen/sich merken ;)

Also, ich habe mal etwas gegoogelt wegen "Reference versus Pointer" - natürlich kommt man damit gleich auf nen C++ Thread bei Stackoverflow:
https://stackoverflow.com/questions/114180/pointer-vs-reference

Und es gibt eine Aussage, ja da fühle ich genau mit:
"References are of course valuable , but i come from C , where pointers are everywhere. One has to be proficient with pointers first to understand the value of references."

Ich komme auch von C und alles was nicht als "Call by value" übergeben wird, ist "call by Reference" und das heißt eigentlich, man arbeitet mit einem "Pointer" auf etwas bzw. in einem Funktionsaufruf - "überträgt eine Adresse" und nicht eine Wertkopie.

Gibt es einen konkreten Unterschied zwischen einer "Referenz" und einem Pointer in C++?
Ich meine, auf Stackoverflow wird geraten (um es kurz zu fassen):
Rule of Thumb: Ein Pointer wird in erster Linie für Pointerarithmetik verwendet und kann auch den Wert Null bzw. "nullptr" (C11) haben, wohingegen eine Referenz relativ "statisch" bzw. diese Features nicht besitzt.

Aber die Antwort hat für mich noch immer nicht erklärt, was konkret eine "Reference" ist... - bzw. kann ich doch auch in eine Referenz schreiben, wenn diese, wie auch ein Pointer, nicht const deklariert ist?
Um es kurz zu fassen, mich irritieren die "Bezeichnungen" bzw. klingt es so als ob man ein paar Schuhe mit zwei Begrifflichkeiten benennt. Vielleicht mag dazu jemand etwas schreiben? ;)

Das hier:
[src=cpp]vector<connection*>* getConnections()[/src]

Habe ich deßhalb verwendet, da in "removeConnection genau der Schreibzugriff nötig und ich keine Kopie zurückgeben wollte, sondern eine "Referenz" (in Form eines Pointers)
Wäre hier eine alternative so etwas wie "getConnectionsWriteable()" ? Und der Rest beim Iterieren und Co, geht über einen "const" return der, gegen Schreibzugriffe, "abgesichert" ist?

Und zu guter letzt, "auto"-Deklarationen ist das eine "Laufzeitabhängige Bestimmung", oder wird diese vom Compiler vorgenommen während alles übersetzt wird? :)

Edit: Habe auch noch das hier gefunden zu Pointer/Reference, aber es sieht eigentlich fast identisch aus:
https://www3.ntu.edu.sg/home/ehchua/programming/cpp/cp4_PointerReference.html
 
Zuletzt bearbeitet:

BurnerR

Bot #0384479

Registriert
20 Juli 2013
Beiträge
5.504
C++ ist wie C statisch typisiert, das gilt auch beim auto keyword.

Hier habe ich eine nette Antwort gefunden: Klick.
Der 2. Punkt
You are sort of mis interpreting how auto should be used. Yes you can do auto i = 2; and it works fine. But a situation where you need auto is a lambda for example. A lambda does not have a namable type (although you can assign it to an std::function). Another situation it is useful for is inside a class or function template it can be extremely difficult to figure out the type of certain operations (maybe sometimes impossible), for example when a function is called on a template type that function may return something different depending on the type given, with multiple types this can become essentially impossible to figure out which type it will return. You could of course just wrap the function in a decltype to figure out the return but an auto is much cleaner to write.
Sowas in der Art habe ich schon einige male gelesen. Ist damit 1:1 das selbe gemeint wie bei dem AAA (Almost Always Auto) Style, die sich ja darauf beziehen [Fragezeichen]:
write code against interfaces, not implementations
Quelle

Das ist auch so ein bisschen am Rand meiner Erfahrungswelt mit C++, vielleicht kann Brother John ja etwas erhellen :D.

Aber die Antwort hat für mich noch immer nicht erklärt, was konkret eine "Reference" ist... - bzw. kann ich doch auch in eine Referenz schreiben, wenn diese, wie auch ein Pointer, nicht const deklariert ist?
ImHo: Du verwendest bei Funktionsaufrufen und als return value immer eine (const) Referenz (statt einem Pointer), außer du willst explizit einen Pointer aus einem ganz konkreten Grund.
Also nur call by reference, nicht call by pointer ;-), wenn du z.B. das Erstellen einer Kopie vermeiden möchtest.
Eine Referenz ist ja einfach ein neuer Name für die Variable. Zeigt also auf den Datentyp / das Objekt, aber ohne diesen ganzen verwirrenden Pointer Overhead.
Solltest du einges Tages mal noch z.B. Java lernen wird das alles ganz klar (nachdem es verwirrender wurde). In Java, abgesehen von den primitiven Datentypen (int, float,...) ist von Haus aus alles eine Referenz (aber damit einem nicht langweilig wird, werden diese Referenzen nur mit call by value an Funktionen übergeben :D).
 
Zuletzt bearbeitet:

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.561
  • Thread Starter Thread Starter
  • #20
@BurnerR: Ich finde Pointer aber nicht verwirrend, ich kann Type* genau so lesen wie Type** - bei allem anderen erschließt sich mir der Sinn aber auch nicht - auch wenn man das endlos fortführen könnte in C, meines Wissens nach. ;)

Und den AAA Artikel habe ich ganz gelesen und ich glaube auch zu verstehen was damit "abstrahiert" wird... es geht alles über das Interface, also, welche Funktion was zurück ist, ist egal, dein Code ist mit "auto" darauf vorbereit und ein Typ kann von int ohne Probleme zu "float" geändert werden.

Allerdings sehe ich auch den Nachteil, wenn ich einen float erwarte, will ich diesen auch haben bzw. gehe ich davon aus? - Habe ich einen Int ist vielleicht eine komplette Rechnung fehlerhaft, weil ich nicht explizit gecastet habe - aber auch das wird "irgendwo" im Artikel erwähnt, bzw. mit "signed und unsigned" Datentypen die man mit "as_signed(wert)" oder "as_unsigned(wert)" schon einmal absichern kann.

Mit Floats hab ich das da glaube ich nicht gelesen, aber das war heute morgen ;)

Edit:
Wobei das mit dem Float auch quatsch ist, man brauch in der Rechnung nur ein "1.0 * (Rechnung)" vorranstellen, schon hat man den Datentypen "Float" oder "Double" erzwungen... je nachdem was "auto" auswählt dafür.
 
Zuletzt bearbeitet:
Oben