Verletzung von Schnittstellen-Anforderungen: Wie behandeln?
-
Grundsätzliches grübeln mal wieder:
// Modul.h: class Modul{ //... public: void Start(){ /*...*/ m_run=true; } void Stop() { /*...*/ m_run=flase; } //... private: bool m_run; };// main.cpp: int main(void){ Modul modul; modul.Start(); // Super, Modul läuft! //... modul.Start(); // Huch, läuft doch schon! //... modul.Stop(); // Aha, fertig also... //... modul.Stop(); // gestoppt, gestoppter, am gestopptesten?! }Folgende Möglichkeiten:
// Modul.cpp: #include <Modul.h> //... void Modul::Start(){ if(m_run) return; // Ignorieren //... m_run=true; } //...// Modul.cpp: #include <Modul.h> //... void Modul::Start(){ if(m_run) throw ModulError("Modul bereits gestartet!"); // Exception //... m_run=true; } //...// Modul.cpp: #include <Modul.h> #include <cassert> //... void Modul::Start(){ assert(!m_run); // Assert //... m_run=true; } //...Welches Design ist am sinnvollsten?
Ich würde zu assert tendieren, da das wiederholte Aufrufen der Funktionen ein Programmierfehler ist, der später im Release weg sein sollte.Die Ignore-Variante wäre hier vielleicht auch möglich, aber man hat die zusätzliche if-Abfrage. Bin anderen Fällen wäre das Ignorieren auch nicht sinnvoll, da ein vielleicht ein Gedankenfehler des Aufrufers nicht bzw. erst später auftaucht.
Die Exception-Variante besitzt auch die ständige if-Abfrage.
Ich bin mir aber einfach nicht sicher...
-
Du könntest die Assertion und die Ignorieren-Methode auch kombinieren. So kriegst du beim Entwickeln die Assertion, und für alle Fälle gehts auch im Release-Build weiter, um Schlimmstes zu verhindern (falls halt mal fehlerhafter Code beim Kunden gelandet ist o.ä.)...
-
Stimmt, das wäre eine sichere Variante.
Besitzt eben nur den (geringen) Nachteil der zusätzlichen if-Abfrage.
Ist das eine gängige und oft anzutreffende Lösung?
-
Roger Wilco schrieb:
Ist das eine gängige und oft anzutreffende Lösung?
Ich kann dir nur sagen, dass ich sowas schon öfter in unserem Source entdeckt habe. Teilweise aber auch in Bereichen, die noch nicht fertig programmiert sind und eigentlich nicht erreicht werden sollten. Wenn der Kunde es doch schafft, soll er soweit wie möglich weitermachen können. Wenn wir an diese Stelle kommen, sollen wir uns daran erinnern, dass da ja noch was zu tun war...

-
Beste/Sauberste Lösung wäre vermutlich, Starten und Beenden von sowas von vornherein wegzukapseln. Siehe RAII.
Das hieße entweder, Start() und Stop() gleich in Konstruktor und Destruktor des Moduls zu integrieren, oder, wenn das nicht möglich ist (z.B. weil das Modul vor dem Starten ggf. noch Informationen verabreicht bekommt) eine eigene Klasse zu schreiben die das übernimmt. Skizze:class Modul{ //... public: void SetSomeParameters(); protected: void Start(){ /*...*/ m_run=true; } void Stop() { /*...*/ m_run=flase; } //... private: bool m_run; }; template <class ModuleToRun> struct ModuleRunner : private ModuleToRun { ModuleRunner(ModuleToRun const& mtr) : ModuleToRun(mtr) { mtr.Start(); } ~ModuleRunner() {mtr.Stop(); } };In der Art wäre dann der ModuleRunner das eigentliche laufende Modul, die Klasse Modul wäre dann eher so eine Art Policy, die bestimmt wie es läuft. Man kanns auch über friends oder auf noch andere Arten lösen, aber ich denke die Grundidee wird klar: Start() udn Stop() nicht mehr frei aufrufen lassen sondern kontrolliert je genau einmal.
-
Wenn Modul::stop() aufgerufen wird, so wird doch nur m_run auf false gesetzt. Danach ist das Modul im Zustand STOP. Wenn nochmals Modul::stop() aufgerufen wird, warum sollte es da Ueberpruefen ob es schon im Zustand STOP ist? Oder gar eine Exception werfen? Eindeutig ein Designfehler! Wenn man etwas stopt, das sich schon in Ruhe befindet, so bleibt es in Ruhe. D.h. Stop sollte nur m_run setzen und sonst nichts anderes. Ich persoenlich modeliere solche Sachen meist als Moore-Automat (seltener als Mealy-Automat).
-
pumuckl schrieb:
class Modul{ //... public: void SetSomeParameters(); protected: void Start(){ /*...*/ m_run=true; } void Stop() { /*...*/ m_run=flase; } //... private: bool m_run; }; template <class ModuleToRun> struct ModuleRunner : private ModuleToRun { ModuleRunner(ModuleToRun const& mtr) : ModuleToRun(mtr) { mtr.Start(); } ~ModuleRunner() {mtr.Stop(); } };Das ist eine gute Idee, pumuckl!
Nur verstehe ich das Beispiel noch nicht ganz: Muss ModuleRunner nicht public von ModuleToRun erben? Und dann hat man das Problem, dass man nur mit einer Kopie von Module arbeitet, statt das Module-Objekt selbst zu starten, oder?
-
Roger Wilco schrieb:
Nur verstehe ich das Beispiel noch nicht ganz: Muss ModuleRunner nicht public von ModuleToRun erben?
Nein, warum?
Und dann hat man das Problem, dass man nur mit einer Kopie von Module arbeitet, statt das Module-Objekt selbst zu starten, oder?
In dem Beispiel stimmt sowieso etwas nicht: Der Parameter mtr wird an die Basis übergeben (in sie kopiert) und dann gestartet. Damit bleibt das Modul-Objekt in der Basis aber ungestartet. Im Destruktor wird ein Objekt mtr benutzt, welches es an dieser Stelle garnicht gibt, da die Klasse keine Membervariablen hat.
Das könnte man wie folgt lösen:
template <class ModuleToRun> struct ModuleRunner { // keine const Referenz mehr, da temporäre Objekte nicht lange genug leben würden ModuleRunner(ModuleToRun& mtr) : m_mtr(&mtr) { m_mtr->Start(); } ~ModuleRunner() {m_mtr->Stop(); } private: ModuleToRun* m_mtr; // Hinweis: auch noch eine gute Idee: Kopieren verbieten };
-
LordJaxom schrieb:
Das könnte man wie folgt lösen:
template <class ModuleToRun> struct ModuleRunner { // keine const Referenz mehr, da temporäre Objekte nicht lange genug leben würden ModuleRunner(ModuleToRun& mtr) : m_mtr(&mtr) { m_mtr->Start(); } ~ModuleRunner() {m_mtr->Stop(); } private: ModuleToRun* m_mtr; // Hinweis: auch noch eine gute Idee: Kopieren verbieten };Auch das ist irgendwo Käse. Wenn du Kopien vermeiden willst, muss mtr auf den Heap. Dann sollte man wegen Vermeidung von Mißverständnissen auch direkt nen Pointer im Konstruktor angeben. Sonst kommt jemand auf den Gedanken, und erstellt mtr auf dem Stack, mit fatalen Folgen für m_mtr, denn der Zeiger wird dann ungültig...
Es kommt halt jetzt drauf an. Sind das wirklich Module im Sinne von Plugin? Oder steht zur Compilezeit fest, welche Module mit rein kompiliert werden? Dann kannst du es über Template und Inheritance machen. Wenn noch andere Methoden außer start und stop zur Verfügung stehen, brauchts ne Public inheritance, außer du stellst diese in ModuleRunner selber noch mal zur Verfügung (fänd ich komisch).
Wenn du dich für den Member entscheidest, musst du dann natürlich auch eine Methode anbieten um auf diesen zugreifen zu können (klar...).
Und dafür brauchst du kein Template, wenn du Modul von ModulAbstractInterface oder so erben lässt.
Wenn du templates verwenden willst, lass doch den Parameter für den Konstruktor komplett weg und überlass der Templateklasse das Erstellen und Zerstören des Modul-Members.
-
l'abra d'or schrieb:
Auch das ist irgendwo Käse. Wenn du Kopien vermeiden willst, muss mtr auf den Heap. Dann sollte man wegen Vermeidung von Mißverständnissen auch direkt nen Pointer im Konstruktor angeben. Sonst kommt jemand auf den Gedanken, und erstellt mtr auf dem Stack, mit fatalen Folgen für m_mtr, denn der Zeiger wird dann ungültig...
Auch bei einem Zeiger im Konstruktor kann ich die Adresse eines Stack-Objekts übergeben. Was im Übrigen nichtmal schlimm ist, das Objekt muss nur länger leben als der Runner. Käse ists trotzdem, da muss ich Dir recht geben, eben weil die Lebensdauer schlecht kontrollierbar ist.
Wenn man das Modul absolut nicht als Bestandteil der Klasse (sei es als Member oder Basis) haben möchte, wäre noch ein shared_ptr< Modul > (oder weak) denkbar. Wenn den jemand mit einem Stack-Objekt initialisieren möchte, muss er sich explizit Gedanken über die Lebensdauer machen.
-
LordJaxom schrieb:
Roger Wilco schrieb:
Nur verstehe ich das Beispiel noch nicht ganz: Muss ModuleRunner nicht public von ModuleToRun erben?
Nein, warum?
Gedankenfehler...

l'abra d'or schrieb:
Es kommt halt jetzt drauf an. Sind das wirklich Module im Sinne von Plugin? Oder steht zur Compilezeit fest, welche Module mit rein kompiliert werden?
Es ist mehr grundsätzlich. Konkret habe ich das z.B. bei einer Timer-Klasse, bei der ich das mehrmalige Starten/Stoppen verhindern möchte und bei Anwendungsmodulen, die bei Programmstart je nach Fall (Aufrufparameter) als Konsolenanwendung oder als Windows-Dienst geladen werden sollen. Dabei möchte ich mich davon freimache, wie (Umgebung) der eigentlich Programmcode läuft. Das sollen die Starter-Klassen machen (Konsole anlegen oder Windows-Dienst-typische Aktionen).
-
Folgender Code läuft bei mir problemlos (VC 6.0).
Nun wollte ich fragen, ob er überhaupt Standard-konform ist, da ich widersprüchliche Sachen bezüglich virtuelle Member-Funktionen von Template-Klassen gelesen habe. Sind die (rein) virtuelle Funktionen in Template-Klassen gestattet? Es funktioniert, aber ist das nur Zufall bzw. liegt es an der Nicht-Standard-Konformität meiner IDE?
template<class T> class Module{ public: Module(){} ~Module(){} public: void Start(){ Thread::Start(&ThreadEntry, this); } virtual void Stop() = 0; protected: struct implementation; boost::shared_ptr<implementation> m_impl; private: // Einstiegsfunktion für Thread static unsigned int __stdcall ThreadEntry(void* param){ static_cast<T*>(param)->Run(); return 0; } };template<class TModule> struct ModuleRunner{ ModuleRunner(Module<TModule>& module){ module.Start(); } ~ModuleRunner(){ Stop(); } void Stop(){ module.Stop(); } };
-
Roger Wilco schrieb:
Sind die (rein) virtuelle Funktionen in Template-Klassen gestattet?
Ja. Klassentemplates sind nur Vorlagen für Klassen; wenn du die Templateparameter einsetzt, entstehen daraus ganz normale Klassen.
Trotzdem rate ich dir davon ab, MSVC++ 6 weiterhin zu verwenden.
-
Nexus schrieb:
Trotzdem rate ich dir davon ab, MSVC++ 6 weiterhin zu verwenden.
Ja, ich würde gerne umsteigen, darf es aber (noch) nicht.
Wenn Du mir Gründe gibst, womit ich meinen Vorgesetzten überzeugen kann, möglichst fix umzusteigen, wäre ich Dir dankbar.
Er schreibt selber seit 20 Jahren C bzw. C++ im Klassen-C-Stil, kennt und nutzt nicht die Standardbibliothek sondern exzessiv die Assistenten von VC++6.0 und MFC/ATL/COM. VC6.0 reicht ihm vollkommen aus (er nutzt nicht einmal eine neue SDK von MS) und als ich ihm mal eine VS2008-Demo zeigte, musste er nur verbittert feststellen, dass seine Programme mit VS2008 nicht mehr kompilieren und die Assistenten ganz anderen Code erzeugen.
Wenn ich Probleme habe, sagt er mir, ich muss ja nicht unbedingt alle Sprachfeatures ausreizen, sondern soll es lieber "einfach" und im C-Stil machen.
Ich muss dazu sagen, dass wir hier mehr technische Software programmieren, die meist als Konsolenanwedung oder Windows-Dienst laufen.
-
Du hast ja bereits einige Gründe genannt (modernes standardkonformes C++, Verwendung der Standardbibliothek), aber wenn dein Vorgesetzter derart stur ist, kann man wohl nicht viel machen.
Du kannst versuchen, ihm weiszumachen, wie veraltet VC 6 ist, was für Bugs die IDE besitzt, und welche Errungenschaften von schönem C++ einem dabei entgehen. Aber wie gesagt, keine Ahnung, obs hilft...

-
Nexus schrieb:
Du kannst versuchen, ihm weiszumachen, wie veraltet VC 6 ist, was für Bugs die IDE besitzt, und welche Errungenschaften von schönem C++ einem dabei entgehen. Aber wie gesagt, keine Ahnung, obs hilft...

Die Kompatibilität zu seinem alten VC6.0-Code ist ihm wichtiger als die Standard-Konformität, da er wie gesagt eh nicht die Standardbibliothek nutzt. Er nutzt lieber MFC/ATL und findet diese "einfacher"...
Es wäre gut eine Quelle zu haben, wo mal einige Bugs und Verbesserungen aufgezählt werden. Ich weiß ja auch nichts genaues, außer dass VC6.0 nicht Standard-Konform ist.
Er sieht halt nur das Geld und sieht für sich keinen Vorteil. Ganz im Gegenteil: Für ihn bedeutet es, dass sein Code entweder portiert werden muss oder dass wir zwei IDEs (6.0 + 9.0) parallel haben müssen. Beides ist mit Zeit und Kosten verbunden.
Was soll ich zu den Argument sagen: "Dann benutze halt MFC, wenn es Probleme mit der Standardbibliothek gibt!" Wie soll ich mit Standard-C++ gegen MFC anstinken, wenn Portabilität keine Bedeutung bei uns hat.
Wenn ich sage, dass MFC ebenfalls einen ordentlichen Versionssprung gemacht hat, sagt er wieder: "Ach, die blähen das mit unnötigen Ballast auf, um nur was neues verkaufen zu können!"
Wenn ich sage, dass ab Feb. 2003 keine PSDK von MS VC6.0 mehr unterstützt und alle neuen WinAPI-Funktionen damit nicht nutzbar sind für uns, entgegnet er, dass man alles auch mit der alten SDK (die mitgelieferte bei VC6.0!) und den alten WinAPI-Funktionen machen kann, da die i.d.R. weiterhin unterstützt werden.
Er kommt halt aus der Assembler und C-Welt und ich finde einfach noch nichts, wo ich sagen kann: Für den Aufwand und das Geld bekommen wir die und die Vorteile.
-
Bei so uneinsichtigen Menschen würde ich mit schon überlegen ob ich dort arbeiten möchte.
-
Sowas nennt man beratungsresistent. Fuer gewoehnlich werden solche Organismen verbrannt. Fire him! Dieser Artikel ist vielleicht recht interessant: http://www.paulgraham.com/icad.html . Im Abschnitt "The Cost of Being Average" wird dargelegt, was es ausmachen kann, sich gegen neue Tools, Sprachen oder Technologien zu straeuben. Auch kann man gerade im Bereich Software nicht stillstehen. In http://www.paulgraham.com/avg.html werden Programmiersprachen verglichen, aber es kann auch allgemeiner fuer Technologien verstanden werden. Ist alles recht Lisplastig, aber ich hoffe das stoert nicht.
-
Wäre interessant herauszufinden ob die alte WinAPI in Vista und dem Kommenden Win7 noch komplett unterstützt wird. Denn spätestens wenn seine 90er-Jahre Programme schlichtweg nichtmehr laufen ist es für ihn an der Zeit sich umzustellen (oder sich auf den Altenteil zurückzuziehen, weil er mit dem Alter scheinbar recht unflexibel geworden ist).
-
Roger Wilco schrieb:
...entgegnet er, dass man alles auch mit der alten SDK (die mitgelieferte bei VC6.0!) und den alten WinAPI-Funktionen machen kann, da die i.d.R. weiterhin unterstützt werden.
Und damit liegt er extrem verkehrt. Er hat wohl nur noch nicht Kunden mit neuen Windowsversionen oder benötigt bestimmte API-Funktionen nicht. Ich kenne solche Probleme, auch wenn mein damaliger Programmierchef dann mit einigen dreckigen Hacks an den Problemen vorbei gearbeitet hat. Interessanterweise sind da wahre Stresssituationen entstanden (Ich hatte den Support recht früh gebeten Vista mit zu testen, doch erst als ein Kunde Probleme hatte wurde dies tatsächlich gemacht).
Viel Spaß wenn dann "urplötzlich" und "unerwartet" bestimmte - teilweise schon im VC6 als deprecated markierte - API-Funktionen nicht mehr das tun, was sie sollen.
-
pumuckl schrieb:
Wäre interessant herauszufinden ob die alte WinAPI in Vista und dem Kommenden Win7 noch komplett unterstützt wird.
Wird sie nicht. Bereits mit Vista (und ich glaube auch XP) gibt es einige API-Befehle die nicht mehr unbedingt laufen (frag mich aber nicht nach den konkreten, ist schon etwas her).