Rückgabewert oder call by reference?



  • Hallo Leute,

    Manchmal stellt sich mir die Frage, ob ich einen Wert per Rückgabewert oder per 'call by reference' übergeben soll. Damit meine z.B. getter-Methoden einer Klasse, aber auch globale Funktionen (bzw. im Namensraum).

    Beispiel:

    typedef struct
    {
    	int   A;
    	float B;
    	bool  C;
    } werte_t;
    
    werte_t meineWerte;
    
    // variante 1
    meineWerte.A = einObjekt.getValueA();
    meineWerte.B = einObjekt.getValueB();
    meineWerte.C = einObjekt.getValueC();
    
    //variante 2
    einObjekt.getValueA(meineWerte.A);
    einObjekt.getValueB(meineWerte.B);
    einObjekt.getValueC(meineWerte.C);
    

    Normalerweise tendiere ich zu Variante 1, da es für mich leichter zu lesen ist. Mein Problem: was mache ich, wenn die Funktion fehlschlägt und wie überpüfe ich es am besten? In oben gezeigten Beispiel könnte 'einObjekt.getValueA()' bei einem Fehler 0 zurückgeben, aber 0 könnte ja auch der Wert sein, wenn die Funktion erfolgreich war. Gut... ich könnte exceptions in der Getter-Funktion werfen, aber irgendwie scheint es für mich zu übertrieben. Gerade wenn ich mehrere unabhängige Werte hintereinander so auslese, würde eine exception den Programmfluss unterbrechen und in die nächste catch-Routine springen.

    Variante 2 könnte z.B. einen Rückgabewert liefern (true bei erfolg; false bei fehler), den ich dann optional prüfen könnte. In meinem Fall meine ich unkrittische Fehler, die eher informativer Natur sind. Schwerwiegende Fehler würden trotzdem exceptions nutzen.

    wie geht ihr sowas an? Nutzt ihr exceptions oder hofft ihr einfach, dass die getter-funktionen immer erfolgreich sind?

    viele Grüße,
    SBond



  • Bei einem Getter ist immer der Rückgabewert zu verwenden. - Was meinst du mit fehlschlagen beim Getter? In der Regel sollte der Getter immer einen Wert zurück liefern der gültig ist bzw. null wenn irgendwelche Auswertungen im Getter den Wert als ungültig definieren. Dann kannst du anschließend deinen Wert auf null prüfen.

    Call by Reference ist eigentlich nur da sinnvoll, wo ein ganzes Objekt manipuliert wird z.B.:

    Object::UpdateFromDB(Data &data)
    {
        // daten ermitteln
        data.WertX = DatenbankwertX;
        data.WertY = DatenbankwertY;
        // ...
    }
    


  • bei einfachen Datentypen sehe ich bei Getter auch keine Probleme, nur bei komplexere Sachen.

    hier mal ein Auszug aus meinem Code:

    // Variante 1
    std::string X509Properties::getX509SubjectName()
    {
        BIO *bioOut = nullptr;
        BUF_MEM *bioBuffer = nullptr;
    	X509_NAME *generalName= nullptr;
    	std::string subjectName;
    
    	generalName = X509_get_subject_name (this->mp_x509);
    	if (generalName == nullptr)
    	{
    		return "";
    	}
    
        bioOut = BIO_new(BIO_s_mem());
    	if (bioOut == nullptr)
    	{
    		return "";
    	}
    
        X509_NAME_print(bioOut, generalName, 0);
        BIO_get_mem_ptr(bioOut, &bioBuffer);
        subjectName = string(bioBuffer->data, bioBuffer->length);
    	BIO_free(bioOut);
    
        return subjectName;
    }
    
    // Variante 2
    bool X509Properties::getX509SubjectName(std::string &dst)
    {
        BIO *bioOut = nullptr;
        BUF_MEM *bioBuffer = nullptr;
    	X509_NAME *generalName= nullptr;
    
    	generalName = X509_get_subject_name (this->mp_x509);
    	if (generalName == nullptr)
    	{
    		return false;
    	}
    
        bioOut = BIO_new(BIO_s_mem());
    	if (bioOut == nullptr)
    	{
    		return false;
    	}
    
        X509_NAME_print(bioOut, generalName, 0);
        BIO_get_mem_ptr(bioOut, &bioBuffer);
        dst = string(bioBuffer->data, bioBuffer->length);
    	BIO_free(bioOut);
    
        return true;
    }
    

    ...aber wahrscheinlich ist Variante 1 besser.



  • Wenn es wirklich nur Erfolg und genau einen Fehlerfall gibt, kann man optional benutzen:

    boost::optional<std::string> X509Properties::getX509SubjectName()
    {
        BIO *bioOut = nullptr;
        BUF_MEM *bioBuffer = nullptr;
        X509_NAME *generalName= nullptr;
        boost::optional<std::string> subjectName;
    
        generalName = X509_get_subject_name (this->mp_x509);
        if (generalName == nullptr)
        {
            return boost::none;
        }
    
        bioOut = BIO_new(BIO_s_mem());
        if (bioOut == nullptr)
        {
            return boost::none;
        }
    
        X509_NAME_print(bioOut, generalName, 0);
        BIO_get_mem_ptr(bioOut, &bioBuffer);
        subjectName = string(bioBuffer->data, bioBuffer->length);
        BIO_free(bioOut);
    
        return subjectName;
    }
    

    Wenn bloß Speicher alle ist, warum nicht das Übliche tun?

    bioOut = BIO_new(BIO_s_mem());
        if (bioOut == nullptr)
        {
            throw std::bad_alloc();
        }
    

    Falls es etwas spezifischer sein soll als none , kann man so etwas mit variant modellieren:

    using openssl_error_code = unsigned long;
    
    eggs::variant<std::string, openssl_error_code> X509Properties::getX509SubjectName()
    {
        BIO *bioOut = nullptr;
        BUF_MEM *bioBuffer = nullptr;
        X509_NAME *generalName= nullptr;
        eggs::variant<std::string, openssl_error_code> subjectName;
    
        generalName = X509_get_subject_name (this->mp_x509);
        if (generalName == nullptr)
        {
            //(ich bin nicht sicher, ob die Funktion überhaupt nullptr zurückgeben kann oder ob die dann den letzten Fehler setzen würde)
            return ERR_get_error();
        }
    
        bioOut = BIO_new(BIO_s_mem());
        if (bioOut == nullptr)
        {
    		throw std::bad_alloc();
        }
    
        X509_NAME_print(bioOut, generalName, 0);
        BIO_get_mem_ptr(bioOut, &bioBuffer);
        subjectName = string(bioBuffer->data, bioBuffer->length);
        BIO_free(bioOut);
    
        return subjectName;
    }
    


  • Hat jetzt nix mit der Frage an sich zu tun, sondern mit Benamsung...

    Wenn eine Funktion getX509SubjectName heisst, dann verspricht sie den "X509 subject name" zurückzugeben. Dann sollte sie das mMn. auch tun. Immer. Ausser sie wirft eine Exception.

    Wenn die Funktion auch nen "Fehlerwert" zurückliefern kann (z.B. wenn es keinen "X509 subject name" gibt), dann sollte sie mMn. eher tryGetX509SubjectName heissen.

    Bzw. wenn man den "X509 subject name" als optionalen Wert ansieht, und nen boost::optional zurückgibt, dann könnte man sie auch getOptionalX509SubjectName nennen.

    Das ganze macht aber mMn. nur Sinn, wenn das "Fehlschlagen" der Funktion als "normal" behandelt werden muss. Weil's halt oft vorkommt und dann lokal (=direkt dort wo die Funktion aufgerufen wird) behandelt werden kann/soll.
    Ist das nicht der Fall, dann sollte die Funktion wohl eher eine Exception werfen.



  • hustbaer schrieb:

    Wenn eine Funktion getX509SubjectName heisst, dann verspricht sie den "X509 subject name" zurückzugeben. Dann sollte sie das mMn. auch tun. Immer. Ausser sie wirft eine Exception.

    ja. Wahrscheinlich hast du recht. 🙂

    vielen Dank euch allen für die Hilfe. 😃


Log in to reply