try und catch sinnvoll einsetzen



  • Hallo zusammen,

    ich habe eine C-Wrapper für einen Labview Interface gechrieben.
    Die Interface an sich funktioniert einwandfrei, nun gefehlt es mir nicht wie die C-Wrapper implementiert ist.

    Bei jede exportierte Funktion wird eine try und catch eigesetzt, was nicht unbedingt der Sinn der OOP ist.
    Einen Beispiel:

    /* Headder*/
    // MeasureStart ist die gewrapperte Class
    extern "C"__declspec (dllexport) int measureWrapper(MeasureStart *LV_ref);
    
    /*cpp*/
    extern "C"__declspec (dllexport) int measureWrapper(MeasureStart *LV_ref)
    {
    	int Error = 0;
    	try
    	{
    		LV_ref->MeasureCont();
    	}
    	catch(QString &msg)
    	{
    		// hier wird eine Nummer zu den Fehler gegeben 
    		// z.B
    		Error = 5006;
    	}
    	return Error;
    }
    ....
    

    // und diese try und catch wiederholt sich bei alle exportierte Funktionen

    Meine Frage wie kann ich das besser machen?



  • try-catch-Blöcke mit OOP in Zusammenhang bringen ist nun ja... nicht sehr glücklich.

    try-catch-Blöcke dienen dazu, erwartete Ausnahmen zu behandeln. Im Fall von deinem Wrapper ist es nun eben so, dass ein Fehlercode festgelegt wird um außerhalb der Wrapper-Funktion zu erkennen, dass etwas schief gelaufen ist.

    Wo du die Ausnahme abfängst ist mMn. dir überlassen, aber abfangen musst du sie. - Ich finde in deinem Beispiel direkt beim Zugriff gar nicht so verkehrt gewählt.



  • nunja, wenn die C interfaces error codes zurück liefern sollen wird man wohl um solche Konstrukte nur schwer herum kommen.

    Macros ?

    ERRNUM_CHECKED(LV_ref->MeasureCont(); 5006) ;
    

    auch nicht schön, aber kürzer

    wobei ERRNUM_CHECKED auch eine Funktion sein könnte die eine Funktion und einen Fehler Code nimmt.

    dummycode

    template <tyename T> // T ist ein callable
    int  err_checked( T f, int errcode)
    {
        try
        {
            f();
        }
        catch(...)
        {
           return errcode ;
        }
        return 0 ;
    }
    
    ....
    
    void somewhere() {
      // undgefähr so
     err_checked( std::bind(LV_ref, LV_Klass::MeasureCont), 5006) ;
    }
    

    das Problem ist freilich das catch(...)
    sollte die geworfene Information nicht immer vom gleichen Type sein und du damit etwas machen wollen, das Problem gilt also auch für die Makro Variante.

    Oder, einfach ein IDE verwenden die den try catch Block für dich schreibt, zumindest das grundgerüsst, geht eigentlich mit allen IDEs / Editoren



  • Ich habe meinen cpp Code um einen class ErrorHandlingWrapper erweitert, die sieht so aus:

    /*Header class ErrorHandlingWrapper */
    typdef enum ErrorList{
    	DiodeVoltageError = 5000,
    	LEDCurrentError,
    	.....
    
    }t_ErrorList;
    
    class ErrorHandlingWrapper
    {
    
    public:
    	ErrorHandlingWrapper(void);
    	~ErrorHandlingWrapper(void);
    
    	int IsThisError(QString &msg);
    
    };
    
    /*cpp Teil*/
    ErrorHandlingWrapper::ErrorHandlingWrapper(void)
    {
    }
    
    ErrorHandlingWrapper::~ErrorHandlingWrapper(void)
    { 
    }
    
    int ErrorHandlingWrapper::IsThisError(QString &msg)
    {
    	t_ErrorList Error = static_cast<t_ErrorList>(0);
    
    	if (!msg.compare(QString("ErrorVoltage/Parameter Error")))
    		Error = DiodeVoltageError;
    		........
    	// hier werden alle mögliche Error in dieses DLL abgefangen
    	// es ist nicht schön gelöst aber auf einen Feadback wird mich freuen
    	....
    }
    

    und dann die nötigen Erweiterungen:

    /*cpp*/
    extern "C"__declspec (dllexport) int measureWrapper(MeasureStart *LV_ref)
    {
    	int Error = 0;
    	ErrorHandlingWrapper* errorHandlingWrapper = NULL;
    	try
    	{
    		LV_ref->MeasureCont();
    	}
    	catch(QString &msg)
    	{
    		if(errorHandlingWrapper->IsThisError(msg))
    		{
    			Error = errorHandlingWrapper->IsThisError(msg); 
    		}
    		else
    		{
    			Error = UnknownErrorConst;		
    		}
    
    	}
    	DeleteAndNull(errorHandlingWrapper); 
    	return Error;
    }
    
    // Aufräumen Arbeit
    template<typename T>
    void DeleteAndNull(T*& pointer)
    {
    	if(pointer != NULL)
    	{
    		delete pointer;
    		pointer = 0;
    	}
    }
    


  • Hallo,

    if(errorHandlingWrapper->IsThisError(msg))
    {
        Error = errorHandlingWrapper->IsThisError(msg);
    }
    

    Denkst du echt, dass es Sinn macht die Methode 2 mal aufzurufen? Cooler wäre doch, wenn IsThisError bereits UnknownErrorConst zurück liefern würde spart doch auch Code?

    extern "C"__declspec (dllexport) int measureWrapper(MeasureStart *LV_ref)
    {
        int Error = 0;
        ErrorHandlingWrapper* errorHandlingWrapper = NULL;
        try
        {
            LV_ref->MeasureCont();
        }
        catch(QString &msg)
        {
            Error = errorHandlingWrapper->IsThisError(msg);  
        }
        DeleteAndNull(errorHandlingWrapper);
        return Error;
    }
    
    int ErrorHandlingWrapper::IsThisError(QString &msg)
    {
        t_ErrorList Error = static_cast<t_ErrorList>(0);
        // Greift keine der unteren if's, wird UnknownErrorConst zurück
        // geliefert.
        Error = UnknownErrorConst;
    
        if (!msg.compare(QString("ErrorVoltage/Parameter Error")))
            Error = DiodeVoltageError;
            ........
        // hier werden alle mögliche Error in dieses DLL abgefangen
        // es ist nicht schön gelöst aber auf einen Feadback wird mich freuen
        ....
    }
    


  • inflames2k schrieb:

    Denkst du echt, dass es Sinn macht die Methode 2 mal aufzurufen?

    Welche Methode 2 meinst du bitte?

    inflames2k schrieb:

    Cooler wäre doch, wenn IsThisError bereits UnknownErrorConst zurück liefern würde spart doch auch Code?

    Du hast vollkommen recht. Da spart man viel Code danke 🙂



  • Siehe z.B. hier:
    https://blogs.msdn.microsoft.com/vcblog/2014/01/16/exception-boundaries/

    template<typename F>
    int wrapper(F&& f)
    	try
    		{
    			f();
    			return 0;
    		}
    	catch(const QString& s)
    		{
    			if(s == "ErrorVoltage/Parameter Error")
    				return DiodeVoltageError;
    			else if (s == ...)
    				return ...;
    			...
    		}
    	catch(...)
    		{
    			return ...; 
    		}
    
    /*cpp*/
    extern "C"__declspec (dllexport) int measureWrapper(MeasureStart *LV_ref)
    {
    	return wrapper([&]{
    			measureWrapper(LV_ref->MeasureCont());
    		});
    }
    

    wrapper könnte auch ein Funktionsobjekt sein - vielleicht mit einer static std::map<QString, ErrorCode> als member?



  • Gerhard_gast schrieb:

    inflames2k schrieb:

    Denkst du echt, dass es Sinn macht die Methode 2 mal aufzurufen?

    Welche Methode 2 meinst du bitte?

    Ich meine keine Methode 2 sondern das 2 malige Aufrufen von IsThisError ;).



  • inflames2k schrieb:

    Ich meine keine Methode 2 sondern das 2 malige Aufrufen von IsThisError ;).

    immer nit kapiert Sorry



  • Warum wirft LV_ref->MeasureCont() einen QString als Exception? Hier fängt das Problem an! Eine Exception sollte immer von std::exception abgeleitet sein. Und sie könnte natürlich gleich eine Fehlernummer mitliefern.

    DeleteAndNull? Das ist einfach nur Müll. Benutze std::unique_ptr. Das alles hilft aber nichts, wenn man dann den NULL-Pointer errorHandlingWrapper deferenziert (so wie in deinem Code).



  • manni66 schrieb:

    Warum wirft LV_ref->MeasureCont() einen QString als Exception?

    Ich habe dieses Projekt geerbt von daher muss ich damit leben

    manni66 schrieb:

    Hier fängt das Problem an! Eine Exception sollte immer von std::exception abgeleitet sein. Und sie könnte natürlich gleich eine Fehlernummer mitliefern.

    manni66 schrieb:

    DeleteAndNull? Das ist einfach nur Müll. Benutze std::unique_ptr. Das alles hilft aber nichts, wenn man dann den NULL-Pointer errorHandlingWrapper deferenziert (so wie in deinem Code).

    Ich benutze Visual Studio 2008 und

    std::unique_ptr
    

    ist leider nicht definiet also ich habe deann zwei Möglichekeiten entwieder einen Macro oder so was in der Art zu implementieren?



  • Gerhard_gast schrieb:

    manni66 schrieb:

    Warum wirft LV_ref->MeasureCont() einen QString als Exception?

    Ich habe dieses Projekt geerbt von daher muss ich damit leben

    Nein, musst du nicht! Statt mit viel Aufwand Strings auf Nummern zu mappen könntest du es einfach richtig machen

    Gerhard_gast schrieb:

    Ich benutze Visual Studio 2008 und

    std::unique_ptr
    

    ist leider nicht definiet also ich habe deann zwei Möglichekeiten entwieder einen Macro oder so was in der Art zu implementieren?

    1. du kannst boost::scoped_ptr oder std::auto_ptr benutzen
    2. ich sehe keinen Grund, warum du an der Stelle überhaupt new/delete benutzt, statt das Objekt auf dem Stack anzulegen. Wobei, new benutzt du ja nicht. Du dereferenzierst einen NULL-Pointer.
    3. deinem Code kann ich keinen Grund entnehmen, warum das Ganze überhaupt in einem Objekt verpackt ist. Eine einfach Funktion würde es auch tun


  • @manni66
    Ich habe deinen Rat gefolgt.

    Wie soll ich die Fehlerbehandlung am geschicktesten designen?

    Fehlerbehandlung heisst:

    1. Fehlererkennung
      und
      2)Fehlerbehandlung
      --> Müssen beide von einander getrennt werden (jeweils eine class)?

    Soll die Fehlerbehandlung zentral sein (einen übergreifende class)?

    --> In meine Appllikation (geerbt von ex Kolleger) sind über 20 class.



  • Gerhard_gast schrieb:

    Meine Frage wie kann ich das besser machen?

    * Dokumentierten Aufzählungstypen statt "magic number" verwenden.
    * Exceptiontypen als _const_ Referenz fangen.
    * sich wiederholenden Code in eine Funktion auslagern.



  • (Anmerkung: Irgendwie scheinst du auf Klassen fixiert zu sein. In C++ muss nicht alles eine Klasse sein.)

    Wie das konkret in deinem Fall aussehen muss, kann man hier sicher nicht klären. Das hängt auch davon ab, wozu der Code eigentlich benutzt wird. Wenn er nur dazu dient, per C-Wrapper eine Funktionalität in Labview zur Verfügung zu stellen (und man im Fehlerfall dort nur einen Fehlercode liefern kann), wäre eine Exception, die nur den Fehlercode liefert, völlig ausreichend. Dort, wo jetzt QString geworfen wird, muss dann halt eine Exception mit dem Fehlercode geworfen werden. In der C-Wrapperfunktion kannst du die Exception fangen und den Code zurückgeben. Das bisschen try/catch/return kann man dann auch einfach duplizieren.

    Als Skizze:

    #include <exception>
    #include <iostream>
    
    enum Errocode
      { NoPermission = 5000,
        NotOpen
      };
    
    struct Ex : std::exception
    {
      Errocode errorcode;
      Ex( Errocode errorcode ) noexcept : errorcode(errorcode){}
    };
    
    struct ExNotOpen : Ex
    {
      ExNotOpen() noexcept : Ex(NotOpen) {}
    };
    
    void f()
    {
      throw ExNotOpen{}; // oder Ex(NotOpen)
    }
    
    int g()
    {
      try{
        f();
      }
      catch( const Ex& e ) {
        return e.errorcode;
      }
      return 0;
    }
    
    int main()
    {
      int erg = g();
    
      std::cout << erg << "\n";
    }
    

Log in to reply