Guter Stil in C++
-
peterchen schrieb:
Hat ja niemand gesagt, daß eine Class Factory einen "nackten" Pointer zurückgeben muß...
Müssen tut man garnichts. Man kann auch ganz ohne Zeiger, Klassen und Schleifen auskommen.
Aber Zeiger sind doch nicht schlecht? Was spricht gegen eine Factory die einen Zeiger liefert? uU mag es durchaus praktisch sein, einen Smartpointer zu liefern - aber nicht immer, weil man nicht immer einen Smartpointer braucht.
Aber man kann ja auch mit Kanonen auf Spatzen schießen...
btw: ha irgendjemand bei Meyers, Sutter, Stroustrup, Alexandrescu, Koenig, etc. je gelesen:
Item XX: Avoid pointers
Ich nicht.
Das sollte doch schon mal Denk-Anregung sein, oder?Und sehen wir uns die C++ Standard Library an: iteratoren sind oft Zeiger. Weil es oft einfach sinnvoll ist. Und selbst ein echter iterator hat intern einen Zeiger - anders geht es garnicht.
Sind iteratoren jetzt schlechter Stil?
-
Shade Of Mine schrieb:
Müssen tut man garnichts. Man kann auch ganz ohne Zeiger, Klassen und Schleifen auskommen.
Ja, man muß nichtmal programmieren.
Thema verfehlt...Shade Of Mine schrieb:
Aber Zeiger sind doch nicht schlecht? Was spricht gegen eine Factory die einen Zeiger liefert? uU mag es durchaus praktisch sein, einen Smartpointer zu liefern - aber nicht immer, weil man nicht immer einen Smartpointer braucht.
Aber man kann ja auch mit Kanonen auf Spatzen schießen...
Wie paßt das jetzt genau mit Deiner sonstigen RAII-Hymne zusammen? Exception-Sicherheit?
Einige weiter unten genannte Personen haben bereits dazu geraten stets smart_pointer zu verwenden.Shade Of Mine schrieb:
btw: ha irgendjemand bei Meyers, Sutter, Stroustrup, Alexandrescu, Koenig, etc. je gelesen:
Item XX: Avoid pointers
Ich nicht.
Das sollte doch schon mal Denk-Anregung sein, oder?jo, vor 10 Jahren gab's auch noch kaum Artikel über exception-Sicherheit. Ich will Dir inhaltlich nicht widersprechen, aber das ist einfach kein wirkliches Argument.
Außerden im CUJ August 2004 steht: "avoid bald pointers" der Artikel ist von Hyslop und Sutter -> Denkanregung???Also ich meide Pointer wo es vernünftig geht, weil sie meiner Ansicht nach fehleranfälliger sind als andere Techniken. Wo man sie braucht, da braucht man sie halt, insbesondere für die Implementierung. Aber gerade aus der Schnittstelle versuche ich sie nach Möglichkeit rauszuhalten.
MfG Jester
-
volkard schrieb:
pointer und referenzen haben unterschiedliche bedeutungen.
Also neulich waren Referenzen noch verkappte Pointer.
-
Aber gerade aus der Schnittstelle versuche ich sie nach Möglichkeit rauszuhalten.
Vielleicht kannst du mir ja helfen.
Wie implementierst du die klassische Strategy-Situation?
Du hast eine Context-Klasse die im Ctor mehrere Policy-Objekte entgegen nimmt.Wenn die konkreten Policy-Objekte nur innerhalb der Context-Klasse gebraucht werden, macht es ja durchaus sinn, wenn man eben diese Klasse für das Lebenszeitmanagement der Policy-Objekte verantwortlich macht. Sprich: Stirbt der Context, sterben die Policies.
Ich reduziere mehrere jetzt mal auf zwei.
Der Ctor nimmt also zwei Zeiger entgegen, der Client hängt bei der Konstruktion an diese zwei Zeiger mit new erzeugte Objekte.
Soweit so gut.
Problem:Context c(new Policy1, new Policy2);
Ah. Da lächelt aber ein hübsches potentielles Resource-Leak.
Hilft es wenn ich die Pointer im Interface durch Smart-Ptr ersetze? Kein Stück. Macht die Situation nur schlimmer.
Context c(smart_ptr<Policy>(new Policy1), smart_ptr<Policy>(new Policy2));
Sieht sicherer aus, hat aber das selbe Problem.
Nächste Idee: Warum nicht einfach auf einen "virtuellen Ctor" zurückgreifen und im Interface mit Referenzen-auf-const arbeiten?
Context c(Policy1(), Policy2()); // in der Ctor-Implementation Context(const Policy& p1, const Policy& p2) : p1_(p1.clone()) , p2_(p2.clone()) {}
Nachteil: Mir geht mein hübscher 0-Wert verloren. Also muss ich extra noch eine NullObj-Policy einführen. Außerdem habe ich jetzt immer zwei Konstruktionen.
Nächste Idee: Policy-Ctoren private/protected machen und für jede Policy eine Factory-Funktion schreiben, die einen Smart-Ptr liefert.
Context c(make_policy1(), make_policy2());
Ok. Hindert aber auch niemanden daran, eigene Policies mit öffentlichem Ctor und ohne Factory-Funktion zu schreiben.
Imo liegt hier das Problem nicht in den Zeigern im Interface sondern im Verhalten des Client-Codes.
Hält sich der Client an einfache Regeln wie "niemals zwei dynamische Objekte in einem Ausdruck anlegen", dann gibt's auch kein Problem:auto_ptr<Policy> a1(new Policy1); auto_ptr<Policy> a2(new Policy2); Context c(a1.release(), a2.release());
Wie löst man das Ganze effizient und so, dass selbst ein VB-Programmierer die Klasse sicher benutzen kann?
-
peterchen schrieb:
Hat ja niemand gesagt, daß eine Class Factory einen "nackten" Pointer zurückgeben muß...
Ich konstruier dir mal einen Fall, indem du einen nackten pointer zurückgeben musst:
#include <vector> template<class T,template<class>class CreationPolicy> class Factory:public CreationPolicy<T>{ public: Factory():CreationPolicy<T>(CreationPolicy<T>()){} Factory(const CreationPolicy<T>& policy):CreationPolicy<T>(policy){} T* create(){return CreationPolicy<T>::create();} }; //policy 1: objekte haben eine längere lebensdauer als die Factory template<class T> class NewCreator{ protected: T* create(){return new T();} }; //policy 2: wenn die Factory zerstört wird, werden sofort alle erstellten objekte mit zerstört template<class T> class VectorCreator{ private: std::vector<T*> ObjHolder; protected: T* create(){ ObjHolder.push_back(new T()); return ObjHolder.back(); } public: void destroy(T* Obj){ for(typename std::vector<T*>::iterator i=ObjHolder.begin();i<ObjHolder.end();++i){ if(*i==Obj){ delete *i; ObjHolder.erase(i); } } } ~VectorCreator(){ for(typename std::vector<T*>::iterator i=ObjHolder.begin();i<ObjHolder.end();++i){ delete *i; } } }; int main(){ Factory<int,VectorCreator> Fac1; int* i=Fac1.create(); Fac1.destroy(i); Factory<int,NewCreator> Fac2; i=Fac2.create(); delete(i); }
da auto_ptr ihr objekt beim ableben zerstören, darf der rückgabewert von create kein auto_ptr sein, da man nicht weis, wie sich die policies verhalten.
was bei Factory<int,NewCreator> funktionieren würde, würde Factory<int,VectorCreator> crashen lassen
-
HumeSikkins schrieb:
Nächste Idee: Warum nicht einfach auf einen "virtuellen Ctor" zurückgreifen und im Interface mit Referenzen-auf-const arbeiten?
Context c(Policy1(), Policy2()); // in der Ctor-Implementation Context(const Policy& p1, const Policy& p2) : p1_(p1.clone()) , p2_(p2.clone()) {}
Nachteil: Mir geht mein hübscher 0-Wert verloren. Also muss ich extra noch eine NullObj-Policy einführen. Außerdem habe ich jetzt immer zwei Konstruktionen.
Den NULL-Wert kannst du an dieser Stelle eh nicht sinnvoll benutzen, weil du im Endeffekt auf eine Default-Policy zurückgreifen willst. Wenn du schon policy-basiert arbeitest, solltest du es auch richtig tun, und dann ist genau dieser Ansatz der einzig sinnvolle.
Im Endeffekt sähe das dann so aus:
struct foo_policy { virtual void foo() = 0; }; struct bar_policy { virtual void bar() = 0; }; struct foo_default_policy : public foo_policy { virtual void foo() { } }; struct bar_default_policy : public bar_policy { virtual void bar() { } }; class policy_user { foo_policy foo_p; bar_policy bar_p; public: policy_user(foo_policy const &fp = foo_default_policy(), bar_policy const &bp = bar_default_policy()) : foo_p(fp), bar_p(bp) { } void do_something() { foo_p.foo(); bar_p.bar(); } };
Ansonsten bist du ja ständig am überprüfen if(foo_policy == NULL) default_stuff(); else foo_policy->stuff(); - ziemlich schlechter Stil imho.
-
Jester schrieb:
Also ich meide Pointer wo es vernünftig geht, weil sie meiner Ansicht nach fehleranfälliger sind als andere Techniken. Wo man sie braucht, da braucht man sie halt, insbesondere für die Implementierung. Aber gerade aus der Schnittstelle versuche ich sie nach Möglichkeit rauszuhalten.
Siehe zB rdbuf() der C++ Streams - wie würdest du soetwas ohne Zeiger lösen?
Oder Listener nehmen auch gerne Zeiger entgegen... Also mir fallen da etliche Situationen ein, wo Zeiger sich sehr gut eignen.Dass man sie meiden soll, wenn es geht und Sinn macht - und wenn es passend ist Smartpointer zu verwenden, halte ich für selbstverständlich. Mich stört nur die Verallgemeinerung.
Bedenke dass auch iteratoren nur Zeiger sind - niemand hindert einem an
*vec.end();
oder ähnlichen späßen. Genau wie bei Zeigern...Volkards Satz: "man meidet nicht pointer um des poitermeidens willen." trifft es IMHO am besten. Es gibt etliche Situationen wo man Zeiger verwenden kann und soll.
-
HumeSikkins schrieb:
Wie löst man das Ganze effizient und so, dass selbst ein VB-Programmierer die Klasse sicher benutzen kann?
Ich wette mit dir, du hättest fast Java-Programmierer geschrieben.
Shade Of Mine schrieb:
Bedenke dass auch iteratoren nur Zeiger sind - niemand hindert einem an
*vec.end();Bist du sicher? Ich bin eigentlich bisher immer davon ausgegangen, dass Iteratoren eines vectors gültig bleiben, wenn man pusht. Das würde aber implizieren, dass der Iterator kein Zeiger sein kann (zumindest nicht direkt auf das Element).
Ich hoffe, ich täusche mich da jetzt nicht.
-
Bist du sicher? Ich bin eigentlich bisher immer davon ausgegangen, dass Iteratoren eines vectors gültig bleiben, wenn man pusht. Das würde aber implizieren, dass der Iterator kein Zeiger sein kann (zumindest nicht direkt auf das Element).
Ich hoffe, ich täusche mich da jetzt nicht.schau dir den std::vector an, da werden die iteratoren beim pushen zum teil auch ungültig, weil ja immer komplett neuer speicher angefordert wird, und die iteratoren aus performancegründen einfache zeiger sind(acuh wenn man sie nicht als solche benutzen darf)
-
Wäre es nicht logischer, Iteratoren als Index zu implementieren?
-
nur im Falle der neuallokierung vom speicher, ansonsten währe es eher kontraproduktiv.
im falle von erase, verlieren auch index-iteratoren ihre gültigkeit, weil sie zuweit zeigen(genauso wie pointer). Dazu sind sie auch noch langsamer ;).
im falle von list-wo auch mit gekapselten pointern gearbeitet wird, wirds sogar noch deutlicher:
wenn du in einer list 3 elemente hast,und würdest dann das 2. element löschen, würde es sich für einen pointer-iterator auf dem 3. element nicht auswirken. die position im speicher ändert sich für den node ja nicht.
ein index operator würde aber gnadenlos ins leere stürzen, weil es keine performante möglichkeit gibt, seinen index mitzukorrigieren(die list hat ja nurnoch 2 elemente, und der index ist 3)
-
Das mit der list zählt jetzt aber nicht. Ich würde bestimmt nicht einen List-Operator als Index implementieren. Aber einen vector-Iterator evtl. schon.
-
Optimizer schrieb:
Das mit der list zählt jetzt aber nicht. Ich würde bestimmt nicht einen List-Operator als Index implementieren. Aber einen vector-Iterator evtl. schon.
Hindert dich ja niemand daran - zumindest fällt mir jetzt kein Grund ein, warum man es nicht so machen darf. Nur ist der Speicherverbrauch doppelt so hoch - weil du dir zusätzlich einen Zeiger auf den vector merken müsstest - das macht die Sache natürlich wieder lahm...
Aber es ändert ja nichts an meiner Feststellung - denn ein vec[vec.size()] macht genauso *PENG*
-
otze schrieb:
jeder kann sich mal im zeichen irren, es gibt sowas,d ass nennt sich flüchtigkeitsfehler^^
Dann kannst du mir ja verraten, welche(s) Zeichen Flüchtigkeitsfehler waren. Meine Kristallkugel ist leider in der Reparatur.
otze schrieb:
wenn du mit tricksen pointer meisnt, dann sind das immernoch keine referenzen
Nee, so hab ich das auch nicht gemeint. Hier nur mal so als schlechtes Beispiel:
int& foo(int& x) { int* tmp = &x; ++tmp; return *tmp; }
-
Und findest du das jetzt guten Stil?
-
groovemaster schrieb:
otze schrieb:
jeder kann sich mal im zeichen irren, es gibt sowas,d ass nennt sich flüchtigkeitsfehler^^
Dann kannst du mir ja verraten, welche(s) Zeichen Flüchtigkeitsfehler waren. Meine Kristallkugel ist leider in der Reparatur.
da war ein & anstatt eines * sonst war der code korrekt.
otze schrieb:
wenn du mit tricksen pointer meisnt, dann sind das immernoch keine referenzen
Nee, so hab ich das auch nicht gemeint. Hier nur mal so als schlechtes Beispiel:
int& foo(int& x) { int* tmp = &x; ++tmp; return *tmp; }
was meinste, was ich mit den pointern meinte? na? richtig, genau das, was du jetzt gemacht hast. Oi! :p
-
0xdeadbeef schrieb:
Den NULL-Wert kannst du an dieser Stelle eh nicht sinnvoll benutzen
Ah. Wie schön das du das weißt ohne den Kontext zu kennen. Ich nutze hier den Nullwert zwar gerade völlig ohne Probleme, aber...
Wenn du schon policy-basiert arbeitest, solltest du es auch richtig tun, und dann ist genau dieser Ansatz der einzig sinnvolle.
Ah. Und wie es richtig ist, dass weißt nur du und das selbst wo du mein tatsächliches Problem gar nicht kennst.
Genau so war das. Design ist eine crispe Wissenschaft und es gibt immer nur eine richtige Lösung. Hatte ich vergessen.
Schade. Eigentlich hatte ich mir von meinem Posting mehr erhofft. Ein paar Ideen oder vielleicht ne Rückfrage.
Über ein simples "du bist doof und so ist's richtig" bin ich ehrlich gesagt längst hinaus. Denn das ich doof ist mir nicht neu und das es nicht die eine richtige Lösung gibt, dass weiß ich nicht erst seit Fred Brooks' "No silver bullet".Im Endeffekt sähe das dann so aus: [...]
Huh? Meine Kontext-Klasse muss den konkreten Typ der Policy kennen? Das gefällt mir aber ganz und garnicht.
Ich sprach vom klassischen Strategy-Pattern. D.h. nicht, dass deine Lösung schlecht ist, sie passt nur nicht zu meinem Problem.Ansonsten bist du ja ständig am überprüfen if(foo_policy == NULL) default_stuff(); else foo_policy->stuff(); - ziemlich schlechter Stil imho.
So schlimm ist es in meiner Situation gar nicht.
Eher so:
if (!filter.get() || (*filter)(entry))
...
Kein else und kein doppelter Boden.
Wichtig: In 97.24% der Fälle ist kein Filter nötig. In diesen 97.24% der Fälle spare ich mir so einen virtuellen Aufruf. Was, wie mir Messungen bestätigt haben, doch was bringt, da dieser Code sehr oft in einer Schleife aufgerufen wird. Noch besser wäre ein Template, da ich dann noch vom inlining profitieren könnte. Leider passt das aber nicht so gut zu meinen anderen Anforderungen.Ich wette mit dir, du hättest fast Java-Programmierer geschrieben.
Die Wette hast du verloren. Ich habe einfach nur mit den Worten von Scott Meyers gesprochen. Aber schön das wir drüber geredet haben.
-
groovemaster schrieb:
otze schrieb:
jeder kann sich mal im zeichen irren, es gibt sowas,d ass nennt sich flüchtigkeitsfehler^^
Dann kannst du mir ja verraten, welche(s) Zeichen Flüchtigkeitsfehler waren. Meine Kristallkugel ist leider in der Reparatur.
Komisch, du korrigierst einen offensichtlichen Flüchtigkeitsfehler und sagst nachher die Kristallkugel ist in Reparatur? Des check i net!
-
@otze: du könntest z.B. boost::shared_ptr mit einem custom deleter verwenden: Policy 1 nimmt den Standard-Deleter, und policy 2 einen NULL-Deleter.
Aber wahrscheinlich baust du mir jetzt schon was mit ner zirkulären Referenz
Nix gegen Pointer, oft genug ist es sinnvoll einen nackten Pointer einzusetzen, aber einen solchen zu verwenden wenn effiziente Alternativen vorhanden sind, halte ich für production code schon für schlecthen Stil.
-
HumeSikkins schrieb:
Ah. Wie schön das du das weißt ohne den Kontext zu kennen. Ich nutze hier den Nullwert zwar gerade völlig ohne Probleme, aber...
Nur, dass etwas funktioniert, heißt nicht, dass es auch guter Stil ist. Den Kontext hab ich mir halt so gut es ging hergereimt, zumal ich davon ausging, dass du nicht über ein konkretes Problem, sondern über policy-basiertes design allgemein sprichst - ich werd nen Teufel tun und hier ne Aura der Unfehlbarkeit anstreben.
Den nächsten Absatz will ich nicht kommentieren, weil destruktiv.
HumeSikkins schrieb:
Huh? Meine Kontext-Klasse muss den konkreten Typ der Policy kennen? Das gefällt mir aber ganz und garnicht.
Wo bitte hab ich das geschrieben? Du musst lediglich den Basistypen sowie eine default-policy kennen. Wenn du ne bessere Möglichkeit für runtime-policies kennst, sags mir.
HumeSikkins schrieb:
So schlimm ist es in meiner Situation gar nicht.
Eher so:
if (!filter.get() || (*filter)(entry))
...
Kein else und kein doppelter Boden.Dass das legaler Code ist, wage ich gerade mal zu bezweifeln - es sei denn, du erwartest von deiner policy, dass sie einen *-Operator hat, und wenn du keine Pointer annimmst.
Wenn filter ein pointer sein und das eigentlich filter->get() heißen soll, musst du NULL-Pointer vorher trotzdem abfangen, womit wir wieder bei unseren ifs wären.
Soviel kann ich dazu sagen, ohne das Problem genauer zu kennen (wobei ich erst jetzt weiß, dass es um ein konkretes Problem geht). Erzähl mir mehr und ich kann dir was sinnvolleres sagen.