Kruze Frage zur Ermittlung der String-Länge
-
Simon2 schrieb:
Habe ich oben schon gebracht. In Kurzform: mit "this->" nagele ich die Entscheidung fest, dass sich das gesuchte Element in meinem Vererbungsbaum befinden muss. Ohne kann ich ein passendes Element auch außerhalb finden => generischer.
Globale Namen gilt es wann immer möglich nicht zu verwenden, das ist ein fundamentaler Grundsatz in der OOP. "this->x" bezieht sich auf Instanzvariable, wenn man nun eine globale Variable hat, dann folgt diese nicht mehr der Semantik einer Instanzvariablen. Wenn ich also so einfach x aus der Klasse auswerfe und durch ein globales x ersetze, habe ich ganz anderes Problem, und ich muß so oder so richtiges Refactoring machen.
Simon2 schrieb:
Was hat das denn damit zu tun ?
Gerade durch die namespaces werden doch Kollisionsmöglichkeiten aufgehoben (und zwar durch ein "using std::x;" exakt so wie ein permanentes "std::x..."Leider stimmt das nicht, denn "x" kann nun "::x" oder "::std::x" sein, wenn beides deklariert ist, welches meinst Du denn?
Simon2 schrieb:
1.) templates sind absolut "typsicher" - genauso typsicher wie "nicht-template-Code", weil der Compiler letztlich "instantiierten Code" mit konkreten Typen sieht.
Nein, typsicher sind sie eben nicht, der Compiler merkt beim Compileren erst an der Stelle an der er eine Methode T::method benötigt, daß T diese Methode gar nicht kennt und es kommt zu den typischen C++ Template Fehlermeldungen. Mit Erfahrung weiß man wo der Fehler liegt, aber Anfänger kompatibel ist das nicht. Typsicher wäre es, wenn man sofort beim Instanzieren der Fehler bekommen würde: MyContainer<> benötigt folgende Methode "method", T stellt diese aber nicht zur Verfügung. Zwar schrebt Stroustrup in [The Design & Evolution of C++, Kapitel 15.4], daß eine solche Maßnahme überflüssig sei, aber wenn dem wirklich so wäre, dann gäbe es nicht solche Projekt wie die Boost Concept Check Library.
2.) Hier sprichst Du Dich halt gegen generische Programmierung wie sie C++ anbietet aus, weil sie Dir zu komplex erscheint.
Ich will das mal so formulieren, weder in "Modern C++ Design" noch "Generative Programming" wird so ein "generischer" Programmierstil propagiert wie Du ihn pflegst.
-
Eine Literaturempfehlung: C++ Templates von Vandervoorde, Josuttis
Simon2 schrieb:
Wenn es Dir so schwer fällt, davon zu abstrahieren, verwende ich zukünftig ein anderes:
// Version 1 #include <HerstellerXLib.h> #using HerstellerXLib::calculate; template <typename T> T f(T t) { T ret = calculate(t); t++; ret += calculate(t); ret.setFlag(); return ret; }Der Begriff Functor ist Dir wohl fremd?
Ich würde Dein Beispiel so umschreiben, kein include notwendig, kein using notwendig.
template <typename T, typename FO> T f(T t) { FO calculate; T ret = calculate(t); t++; ret += calculate(t); ret.setFlag(); return ret; }Oder man löst das über eine Traits bzw. Policy Class
template <typename T, typename Calculator> T f(T f) { T ret = Calculator::calculate(t); t++; ret += Calculator::calculate(t); ret.setFlag(); return ret; }Auch hier ist weder ein using noch ein include notwendig. Daher gibt es weniger Abhängigkeiten.
-
~john schrieb:
Nein, typsicher sind sie eben nicht, der Compiler merkt beim Compileren erst an der Stelle an der er eine Methode T::method benötigt, daß T diese Methode gar nicht kennt und es kommt zu den typischen C++ Template Fehlermeldungen. Mit Erfahrung weiß man wo der Fehler liegt, aber Anfänger kompatibel ist das nicht. Typsicher wäre es, wenn man sofort beim Instanzieren der Fehler bekommen würde: MyContainer<> benötigt folgende Methode "method", T stellt diese aber nicht zur Verfügung. Zwar schrebt Stroustrup in [The Design & Evolution of C++, Kapitel 15.4], daß eine solche Maßnahme überflüssig sei, aber wenn dem wirklich so wäre, dann gäbe es nicht solche Projekt wie die Boost Concept Check Library.
Der Compiler merkt bei der Instanziierung, wenn ein Typ nicht die geforderten Operationen hat. Daß die daraus resultierenden Fehlermeldungen etwas kryptisch sein können, steht auf einem anderen Blatt (und Lösungen wie Concept Check dienen auch nur dazu, die Fehlermeldungen lesbarer zu gestalten).
~john schrieb:
Der Begriff Functor ist Dir wohl fremd?
Ich würde Dein Beispiel so umschreiben, kein include notwendig, kein using notwendig.
Ist dir schonmal in den Sinn gekommen, daß die verwendeten Fremdbibliotheken nicht unbedingt auf deinen Programmierstil ausgerichtet sind?
-
Simon2 schrieb:
Runde 126 schrieb:
Simon2 schrieb:
ich empfinde die Verwendung von "this->" (mal abgesehen von der meistens unnötigen Tipparbeit) eine Einschränkung der Generizität ... ebenso wie die direkte Qualifizierung mittels std:: - und lasse Beides deswegen (und die "m_"-Notation ebenfalls).

Was hat den this und std:: mit Generizität zu tun?
Ma legt über den Namen hinaus bereits einen "Gültigkeitsbereich" (meint nicht C++-Scope - finde gerade keine bessere Bezeichnung) fest.
Was, wenn in einer späteren Version nicht mehr std::cout, sondern das semantisch identische myOwn::cout verwendet werden soll ?
Was, wenn eine Membervariable später "ausgelagert" wird (z.B. in den globalen Namensraum) ?=> Jeweils an 1000 Codestellen rumändern, statt da, wo es hingehört: In der Deklaration:
Also ich weiß ja, dass es in C++ keine richtigen Interfaces gibt, dafür kann man ja rein virtuelle Klassen machen. Aber dass man den namespace als Interface missbraucht ist mir neu und hat nichts mit Generizität zu tun. Das haut vielleicht bei cout hin, weil da alle Methoden jedem geläufig sind und es wahrscheinlich wieder mit den selben funktionen neu implementiert wird, wenn man es will. Aber wenn man sich mal sowas wie std::vector und math::vector vorstellt, dann hat das garnichts mit Interface zu tun.
// Version 1: // A.h struct A { int x, y; A(); void f() const; void g(); }; // A.cpp #include <iostream> using std::cout; A::A() : x(0), y(0) {} void A::f() const { cout << x; } void A::g() { f(); ++y; }Zwischenzeitlich haben sich die Anforderungen ein wenig geändert, so dass f() und x eigentlich nicht mehr viel mit A zu tun haben.
// Version 2: // A.h struct A { int y; void g(); }; // A.cpp #include <MyTools> using myTools::cout; static int x = 0; void f() { cout << x; } // Code identisch mit oben void A::g() { f(); ++y; } // Code identisch... später alles ausgelagert:
// Version 2: // A.h struct A { int y; void g(); }; // A.cpp #include <MyTools> using myTools::f; void A::g() { f(); ++y; } // Code identischDas wäre mit this->x und this->f() mehr Getippe (mit mehr Fehlerpotential) geworden.
Ich bin zwar auch nicht dafür überall this-> hinzuschreiben. Aber das hat nichts mit Generizität zu tun, sondern ist nur ein "Trick", damit du einfacher Refactoren kannst. Wenn du ein richtiges Refactoring Tool hättest (Wenns so ein gibt), dann bräuchtest du sowas nicht.
-
CStoll schrieb:
Ist dir schonmal in den Sinn gekommen, daß die verwendeten Fremdbibliotheken nicht unbedingt auf deinen Programmierstil ausgerichtet sind?
Wir waren eben bei dem Problem, daß die Verwendung von qualifizierten Namen angeblich die Generizität des Codes verringern würde. Wenn eine Fremdlibrary in einem deutlich anderen Stil geschrieben wurde: Wie wahrscheinlich ist es dann, daß ich sie ohne Wrapper als Substitution für eigenen Code verwenden kann?
Zum Stil: Da die Standard Library exakt dasselbe macht, ist das bei neueren Libraries mittlerweile unwahrscheinlicher als früher, aber es ist leider nicht auszuschließen, daß es zu Problemen kommt. Trotzdem halte ich es für erstrebenswert konfigurierbare Eigenschaften von Klassen auch durch deren Parametrisierung zum Ausdruck zu bringen. Eine Wrapperklasse für einen Funktionszeiger ist schnell geschrieben, so daß dieser wie ein Functor genutzt werden kann.
-
~john schrieb:
...
Globale Namen gilt es wann immer möglich nicht zu verwenden, das ist ein fundamentaler Grundsatz in der OOP. ....Seltsam nur, dass ich in keinem der von mir gelesenen Bücher (2x"exceptional", 2x"effective", "Gotchas", "C++ Templates: The Complete Guide" oder in "imperfect C++") dieser "fundamentale Grundsatz der OOP" erwähnt wird, noch sich in deren Codesamples niederschlägt.
~john schrieb:
...
Nein, typsicher sind sie eben nicht, der Compiler merkt beim Compileren erst an der Stelle an der er eine Methode T::method benötigt, daß T diese Methode gar nicht kennt ...Und wann merkt der C++-Compiler das bei "normalen" Typen ?
~john schrieb:
...
Der Begriff Functor ist Dir wohl fremd?...
Wieder mal so ein "Dramaqueensatz" - verkneif Dir die doch einfach mal.
Ganz allgemein gilt: Dass es noch andere Techniken gibt, ist in keinster Weise als "Gegenbeweis".
Meine Aussage ("das geht ohne Codeveränderung => mein Ansatz ist generischer") gilt natürlich ebenso für Funktoren - dass Du Deinen Code ändern kannst, um denselben Effekt zu erzielen, hattest Du schon mit dem "Suche&Ersetzen"-Ansatz belegt (und von mir unbestritten).Da ich das Gefühl habe, dass Du meinen Satz die letzten 3 mal nicht gelesen hast (die Hoffnung stirbt zuletzt): Verwendung von "this->/std::" definieren stärker und schränken deswegen die Flexibilität ein - das hast Du bislang in keinsterweise widerlegt, sondern lediglich dargelegt, warum Du persönlich diese Flexibilität nicht möchtest.
Gruß,
Simon2.
-
@Simon2:
Global ist Böse. Das ist so. Ob das mit OOP zu tun hat ist eine andere Frage (IMO hat es VIEL mit OOP zu tun), aber Global ist Böse.
-
Simon2 schrieb:
Seltsam nur, dass ich in keinem der von mir gelesenen Bücher (2x"exceptional", 2x"effective", "Gotchas", "C++ Templates: The Complete Guide" oder in "imperfect C++") dieser "fundamentale Grundsatz der OOP" erwähnt wird, noch sich in deren Codesamples niederschlägt.
Das sind auch nicht gerade Bücher zum Thema OOP, sondern Bücher die sich mit den Besonderheiten von C++ befassen, deren Hauptthemengebiet ist C++ Codingstyle. C++ ist eine Hybridsprache, wo zum Teil widerstrebende Konzepte genutzt werden. Daher gibt es die Möglichkeit globale Variablen zu nutzen, ist es deshalb aber erstrebenswert globale Variablen zu nutzen?
Simon2 schrieb:
~john schrieb:
...
Nein, typsicher sind sie eben nicht, der Compiler merkt beim Compileren erst an der Stelle an der er eine Methode T::method benötigt, daß T diese Methode gar nicht kennt ...Und wann merkt der C++-Compiler das bei "normalen" Typen?
Um das mit dem typsicher zu präzisieren: Templates sind typsicher in dem Punkt, das man jeder Instanzierung nur den Typ T benutzen kann, aber der Vorgang des Instanzierung ist es nicht.
Beispiel:#include <vector> class MyClass { MyClass& operator= (MyClass const& rhs) { return *this; } }; int main () { std::vector<MyClass> vec(10); // <- hier sollte es eine Fehlermeldung geben vec.erase(vec.begin()); // <- aber erst hier knallt's }Was heißt für Dich in diesem Kontext "normaler Typ"? Wenn Du ein X einem Y zuweisen willst und keine Konversion möglich ist, meckert der Compiler gleich. Wenn für eine Funktion Klasse A verlangt wird und Du Klasse B übergibst, dann meckert der Compiler gleich wegen eines Typfehlers und nicht erst am Punkt, daß B diese und jene Methode nicht unterstützt.
Simon2 schrieb:
Meine Aussage ("das geht ohne Codeveränderung => mein Ansatz ist generischer") gilt natürlich ebenso für Funktoren
Ich halte das Wort "generisch" in diesem Kontext für vollkommen unpassend, da es hier keine definierte Schnittstelle bzw. Kontrakte gibt. Codeersetzung per Cut&Paste fällt für mich auch nicht unter "generisch" das ist Refactoring, und dann muß man anschließend die notwendige Sorgfalt walten lassen. Wenn man einen Funktor oder eine Policy Class nutzt, dann gibt es in der Code Dokumentation eine definierte Schnittstelle für die Ersetzung des Codes. Bei dem was Du machst gibt es diese Softwareschnittstelle definitiv nicht. -> Refactoring
Simon2 schrieb:
Da ich das Gefühl habe, dass Du meinen Satz die letzten 3 mal nicht gelesen hast (die Hoffnung stirbt zuletzt): Verwendung von "this->/std::" definieren stärker und schränken deswegen die Flexibilität ein
Zu diesem Punkt habe ich eine diametral differiende Meinung. Denn:
- Eine Änderung von einer Instanzvariablen mit "this->" zu einer globalen Variablen entspricht einem Wechsel von einer Instanzvariablen zu einer Klassenvariablen. -> Das ist Refactoring. Du sparst Dir etwas Tipparbeit beim Refactoring, aber es ist kein generisches Programmieren.
- Damit der Wechsel von "using namespace std" zu einem "using namespace MyNamespace" funktioniert, muß der komplette Inhalt von "std::" nachprogrammiert werden in einer alternativen Implementation. Das wird wohl kaum gemacht werden. Ergo ist die Bindung an "std::" sehr viel stärker als Du Dir eingestehen willst. Das mag bei einzelnen Konstrukten anders sein (z.B. in dem man leicht "std::cout" durch einen andere "std::ostream" ersetzen kann), aber warum bringst Du das nicht durch das Design zum Ausdruck? Ich sehe auch hier außer eingesparten Tipparbeit keinen Sinn in der Nicht-Qualifizierung. Es erhöht aber die Gefahr von Mehrdeutigkeiten von Bezeichnern. Man kann nicht mehr zwischen "::std::Bezeichner", "::Bezeichner" und "::MyNamespace::Bezeichner" unterscheiden. Wenn jetzt auch noch derselbe Bezeichner in mehreren Namensräumen auftritt hat auch der Compiler ein Problem. In so einem Fall muß man qualifizieren, um den richtigen Bezeichner auszuwählen. (Warum macht man das nicht gleich?) Allerdings erschwerst Du jedem anderem Programmieren das Lesen des Sourcodes. Wenn es sich bei "Bezeichner" zufälligerweise um "vector<T>" handelt, woher soll der Leser des Codes erkennen können, aus welchen Namesraum das nun stammt? Vielleicht war jemand so klug und hat einen eigenen "::MyNamespace::vector<T>" programmiert, der aber nur ähnlich zu "std::vector<T>" ist, aber nicht wirklich kompatibel mit "std::vector<T>" ist und das funktioniert nur weil "#include <vector>" nicht erfolgte. Ich würde in so einem Fall "MyNamespace::vector<T>" im Sourcecode bevorzugen, damit sich das deutlich von "std::vector<T>" abhebt.
Fazit: Du spartst Dir Tipparbeit: Das ist ein legitimes Anliegen. Dafür nimmst Du Mehrdeutigkeiten in Kauf. Flexibler ist Dein Ansatz aber nicht, da die Annahmen über Eigenschaften des Codes nur implizit gemacht werden und nicht explizit. An der Stärke der Bindung des Codes an die Standard Library ändert dies nichts.
Grüße
-
~john schrieb:
...
Das sind auch nicht gerade Bücher zum Thema OOP, sondern Bücher die sich mit den Besonderheiten von C++ befassen, deren Hauptthemengebiet ist C++ Codingstyle....Und ich dachte, genau darum ginge es hier.

Was sind denn "this->/std::" sonst für Vorgaben, wenn nicht C++-Codingstyle-Vorgaben ?BTW: Ich denke schon, dass Sutter, Meyers & Co nennenswert Ahnung von OOP UND generischer Programmierung (um die es hier eigentlich ging) haben.
~john schrieb:
...ist es deshalb aber erstrebenswert globale Variablen zu nutzen?...
Habe ich nie behaupet.
Es ging mir um Namen (die der Compiler je nach Rahmenbedingung an unterschiedliche Objekte binden kann) nicht um "Variablen".Simon2 schrieb:
....Um das mit dem typsicher zu präzisieren: Templates sind typsicher in dem Punkt, das man jeder Instanzierung nur den Typ T benutzen kann, aber der Vorgang des Instanzierung ist es nicht....
Was Du verlangst, bietet C++ halt nicht ... aber das bietet es auch bei "normalen Typen" (also Nicht-templates, oder sagen wir: "konkreten Typen") auch nicht: Du kannst nirgends vor ihrer Definition für eine Klasse vorgeben, welche Eigenschaften sie haben soll.
Wenn Du das brauchst, musst Du Dir eine andere Sprache suchen.~john schrieb:
...
Wenn Du ein X einem Y zuweisen willst und keine Konversion möglich ist, meckert der Compiler gleich. Wenn für eine Funktion Klasse A verlangt wird und Du Klasse B übergibst, dann meckert der Compiler gleich wegen eines Typfehlers und nicht erst am Punkt, daß B diese und jene Methode nicht unterstützt. ..."gleich" = da, wo die Zuweisung (sprich der Aufruf eines operator=()) stattfindet
"meckern" = "keine passende Funktion definiert" (hier z.B. operator=())
Da besteht überhaupt kein Unterschied, ob man das mittels templates macht oder nicht.~john schrieb:
...Ich halte das Wort "generisch" in diesem Kontext für vollkommen unpassend, da es hier keine definierte Schnittstelle bzw. Kontrakte gibt. ...
Aha - anscheinend brauchst Du für "Generizität" Kontrakte (und das, was Dir C++ derzeit bietet reicht Dir anscheinend nicht). OK, dann kann man mit C++ eben überhaupt nicht "~john-generisch" programmieren (vielleicht ja im neuen Standard).
Dann brauchen wir uns aber auch nicht über ein "mehr oder weniger" an Generizität zu streiten.~john schrieb:
...das ist Refactoring, ...
Kannst Du von mir aus so nennen. Einen Code, der einen "logischen Zusammenhang definiert, der für (nahezu) beliebige Implementierungen identisch bleibt", nenne ich halt "generisch".
~john schrieb:
...
[*]..."using namespace std"...Mooooment !
Hast Du einfach mein Anliegen nicht richtig gelesen ? Ich sprach NIE von "using namespace" !! Und das war kein Zufall !
"using namespace" mache ich höchstens bei Miniprogrämmchen.
Ich plädiere für ein "using myNamespace::f" (immer schon).
Und damit muss ich nur genau die Schnittstelle von f bedienen, bzw. wenn ich das möchte, KANN ich es mit meiner Vorgehensweise ohne Änderung des Nutzcodes.
("using namespace" ist für mich allein deswegen, weil "jeder reinschmeissen kann, was er will", in dem Zusammenhang ein no go).~john schrieb:
...Flexibler ist Dein Ansatz aber nicht, da die Annahmen über Eigenschaften des Codes nur implizit gemacht werden und nicht explizit....
Was hat denn der Grad an Flexibilität mit "impliziten/expliziten Annahmen der Codeeigenschaften" zu tun ?
Das kann doch höchstens in die (von mir schon seit 2 ThreadsSeiten bei Dir vermutete) Richtung gehen: Die Flexibilität ist schwer zu handhaben, oder ?Gruß,
Simon2.
-
Simon2 schrieb:
Was sind denn "this->/std::" sonst für Vorgaben, wenn nicht C++-Codingstyle-Vorgaben?
Es geht vor allem um das Design einer Applikation, und wie man das mit C++ möglichst optimal umsetzen kann. Der Codingstyle ist dabei nur ein Aspekt von vielen.
Mir geht es darum, daß bei dem von Dir vorgeschlagenen Weg man nicht sieht, daß es eine konfigurierbare Option im Sourcecode gibt. Wenn man das ganze über ein "typedef" löst ist das schon besser, da man dies zumindest in der betreffenden Datei sieht. Das hat aber den Nachteil, daß man das nicht in der API einer Klasse sieht. Wählt man stattdessen den Weg über eine Option (z.B. Übergabe eines Delegates) im Konstruktor der Klasse oder einer Policy-Klasse, dann sieht das auch jeder in der API der Klasse. Das ist deutlich besser.
Mehr Flexibilität hat man mit diesen Lösungsansätzen auch. Deine Methode erlaubt nur eine Variante im Programm (u.a. deshalb halte ich sie für nicht generisch). Mit Policy-Klassen kann man mehrere Varianten im Programmcode benutzen. Ergo, weder die Verwendung von qualifizierten Bezeichner noch die Benutzung von "this->" reduzieren die Flexibilität, wenn man sein Programm entsprechend entwirft.
-
Hi,
ich glaube, mir geht ein Licht auf: Wir haben unterschiedliche Szenarien vor Augen:
- Du fragst Dich, wie man eine "Struktur" (Klasse(ntemplate), Funktion(stemplate), ...) möglichst flexibel entwirft.
- Ich frage mich, wie man vorgegebene "Struktur" möglichst flexible einsetzt.Ich habe gar nichts gegen die von Dir eingesetzten Techniken und würde sie auf jeden Fall auch nutzen .... aber sie sind keine "Antwort auf meine Frage". Die Möglichkeiten von Policies, Delegates, .... sind prima, helfen aber nichts, wenn die vorgegebene Lib sie aber nicht anbietet (oder zumindest nicht in der benötigten Form). Deine und meine Vorschläge schließen sich gar nicht aus, sondern ergänzen sich. Man kann auch nicht von "besser" sprechen, weil ich natürlich meine eigene Implementierung nur "unterschieben" würde (mittels "using MyZeug::f"), wenn die vorgegebene Struktur nicht bietet, was ich brauche.
Ich spreche NICHT davon (und tat das nie), eine "Struktur" so zu entwerfen, dass sie ausschließlich über "this->/std::" flexibilisiert werden kann. Ich plädiere nur dafür, diese Form nicht von vorneherein auszuschließen (oder nur da, wo es unbedingt sein muss) - denn das war der ursprüngliche Vorschlag ("immer this-> und immer std::").
Gruß,
Simon2.