Problem mit Vector



  • Ich möchte gerne eine Methode in einer Klasse schreiben die mir 2 Werte zurück gibt. Da ich das normal mit assoziativen Arrays aus PHP kenne war das meine erste Wahl. Leider gibt es sowas wohl nicht standardmäßig in C++ und habe dann Vektoren genommen. Jetzt habe ich aber das Problem, dass das Programm an der Stelle abbricht wo ich den Vektoren Werte zuweise. Es gibt leider auch keine Fehlermeldung, dass Programm beendet sich einfach ohne weiteren Code auszuführen.

    std::vector<std::string> trashrm::Database::restore(std::string object)
    {
        sqlite3_stmt *statement;
        std::vector<std::string> row;
        std::string sql;
        if (object.compare(0, 2, "id:") == 0)
        {
            sql = "SELECT * FROM trashrm WHERE ID=" + 
                    object.substr(3, object.length() - 1) + ";";
        }
        else
        {
            sql = "SELECT * FROM trashrm WHERE OBJECTNAME='" 
                    + object + "';";
        }
        rc = sqlite3_prepare(db, sql.c_str(), -1, &statement, 0);
        if (rc == SQLITE_OK)
        {
            int res = 0;
            res = sqlite3_step(statement);
            if (res == SQLITE_ROW)
            {
                row[0] = (char*)sqlite3_column_text(statement, 1);
                row[1] = (char*)sqlite3_column_text(statement, 3);
            }
            if (res == SQLITE_ERROR)
            {
                std::cout << "Error: " << sqlite3_errmsg(db) << std::endl;
            }       
        }
        else if (rc == SQLITE_ERROR)
        {
            std::cout << "Database Error: " << sqlite3_errmsg(db) << std::endl;
        }
        sqlite3_finalize(statement);
        return row;
    }
    

    das problem liegt wohl hier

    row[0] = (char*)sqlite3_column_text(statement, 1);
                row[1] = (char*)sqlite3_column_text(statement, 3);
    

    Was mache ich falsch?



  • Du musst den Vector anweisen, Speicher zu besorgen. Wenn du einen Vector mit

    vector<std::string> row;
    

    erstellst, kannst du nicht einfach so auf ein Element zugreifen, da dieses noch nicht existiert.
    Das machst du aber in Zeile 23 und 24.
    Daher ändere das

    row[0] = ...
    

    zu

    row.push_back(...) // zum Beispiel
    

    Es gibt noch andere Möglichkeiten, so kannst du beispielsweise bei der Konstruktion des Vectors gleich die Anzahl Elemente, die du brauchst (wenn du die kennst) mit angeben, oder auch den vector resize()en.
    Siehe http://en.cppreference.com/w/



  • std::vector<std::string> row;
    

    erzeugt einen leeren vector.
    Um Daten in den vector einzufügen benutze push_back.



  • Das wars. Vielen Danke

    Finde ich echt schade das es nicht so was einfaches wie assoziative Arrays gibt. Kann man sich bestimmt selber basteln.


  • Mod

    Bennisen schrieb:

    Finde ich echt schade das es nicht so was einfaches wie assoziative Arrays gibt. Kann man sich bestimmt selber basteln.

    Gibt es doch. Guck mal unter den assoziativen Containern:
    http://en.cppreference.com/w/cpp/container



  • Bennisen schrieb:

    Finde ich echt schade das es nicht so was einfaches wie assoziative Arrays gibt. Kann man sich bestimmt selber basteln.

    Du suchst wohl std::map/std::unorderd_map . Allerdings sind diese Klassen komplizierter als du vielleicht vermutest (du kannst dir ja mal selbst einen Red-Black Tree oder eine Hash Map implementieren). Außerdem lohnt sich für 2 Werte kein assoziativer Container. Wenn es wirklich immer nur zwei Werte sind würde schon ein std::pair ausreichen.



  • 2 Werte zurückgeben wird in C++ aber eher durch Referenzen gelöst. Also man gibt die Rückgabevariablen als parameter in der Funktion an (als Referenz). Der Rückgabewert dann vielleicht noch als Fehlercode.
    Dann musst du den vector später auch nicht mehr auseinandernehmen.



  • Bengo schrieb:

    2 Werte zurückgeben wird in C++ aber eher durch Referenzen gelöst.

    Naja das war eher früher™. Seit es Return Value Optimization und Move Semantics gibt ist Return By Value wieder in.



  • Also ist

    vector<string> function(string name);
    

    schneller als

    int function(string name, string& fristRetrun, string& secoundReturn);
    

    ?
    Kommst mir komisch vor, und zweites ist finde ich auch einfacher zu nutzen.



  • Die 2 Werte hole ich aus einer Datenbank und dafür habe ich eine Klasse. Eine andere Klasse ruft dann die Methode auf um die Daten abzufragen. Aber ich könnte auch die 2 Werte in der Klasse als Publicvariablen deklarieren und über die Klasse drauf zugreifen. Finde das mit dem return eigentlich besser aber lasse mich auch gerne eines besseren belehren 🙂



  • Bengo schrieb:

    Also ist

    vector<string> function(string name);
    

    schneller als

    int function(string name, string& fristRetrun, string& secoundReturn);
    

    ?
    Kommst mir komisch vor, und zweites ist finde ich auch einfacher zu nutzen.

    Naja wegen dem vector wohl eher nicht. Aber die Variante mit pair sollte in den meisten Fällen genauso schnell sein und ich finde diese Variante einfacher. Man vergleiche:

    auto v = function("something");
    cout << v.first << " " << v.second << endl; // Do something with return values
    
    string first, second;
    function("something", first, second);
    cout << first << " " << second << endl; // Do something with return values
    

    Nur in den meisten Fällen genauso schnell, weil die Variante mit Referenzen den Vorteil hat vorhandene Objekte wieder zu benutzen. Wenn der übliche Anwendungsfall von function in einer Schleife ist, dann wären Referenzen besser.

    Bennisen schrieb:

    Die 2 Werte hole ich aus einer Datenbank und dafür habe ich eine Klasse. Eine andere Klasse ruft dann die Methode auf um die Daten abzufragen. Aber ich könnte auch die 2 Werte in der Klasse als Publicvariablen deklarieren und über die Klasse drauf zugreifen. Finde das mit dem return eigentlich besser aber lasse mich auch gerne eines besseren belehren 🙂

    Wenn du bereits eine Klasse hast um diese 2 Werte zu speichern, dann nutz doch ein Objekt dieser Klasse als Rückgabewert. Vorausgesetzt die Klasse speichert wirklich nur diese beiden Werte und nich noch zu viel drumherum. Schon in C waren structs eine Möglichkeit um mehrere Rückgabewerte zu ermöglichen.



  • Keine Sorge, ich glaube jeder baut seine ersten SQL-Statements händisch zusammen. Sicherer und gängiger ist es aber die sqlite3_bind_* Befehle zu benutzen. Insbesonders um sich gegen SQL-Injection zu verteidigen.

    Darüberhinaus würde ich auch gar nicht anfangen, einen String der übergeben wird auf bestimmtes Format zu parsen. Nimm doch eine überladene Funktion.

    #include <iostream>
    #include <string>
    #include <tuple>
    #include <sqlite3.h>
    
    using namespace std;
    
    sqlite3* db;
    
    tuple<string, string> get(int id){
      const char* sql = "SELECT name, value FROM test WHERE id=?";
      sqlite3_stmt *stmt;
      sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr);
      sqlite3_bind_int(stmt, 1, id);
      auto res=SQLITE_ROW==sqlite3_step(stmt) ?
        tuple<string, string>{ reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0)),
    			   reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1)) }
      : tuple<string, string>{};
      sqlite3_finalize(stmt);
      return res;
    }
    
    tuple<string, string> get(string name){
      const char* sql = "SELECT name, value FROM test WHERE name=?";
      sqlite3_stmt *stmt;
      sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr);
      sqlite3_bind_text(stmt, 1, name.c_str(), -1, nullptr);
      auto res=SQLITE_ROW==sqlite3_step(stmt) ?
        tuple<string, string>{ reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0)),
    			   reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1)) }
      : tuple<string, string>{};
      sqlite3_finalize(stmt);
      return res;
    }
    
    int main() {
      const char* sql = "CREATE TABLE test(id, name, value);"
        "INSERT INTO test VALUES"
        "(0, 'obj0', 'val0'), (1, 'obj1', 'val1'), (2, 'obj2', 'val2');";
      sqlite3_open(":memory:", &db);
      sqlite3_exec(db, sql, nullptr, nullptr, nullptr);
      string s1, s2;
      tie(s1, s2) = get(0);
      cout << s1 << ' ' << s2 << endl;
      tie(s1, s2) = get("obj2");
      cout << s1 << ' ' << s2 << endl;
      sqlite3_close(db);
    }
    


  • Hier mal ein Benchmark für verschiedene Methoden:

    #include <string>
    #include <utility>
    #include <chrono>
    #include <iostream>
    
    using namespace std;
    
    pair<string, string> function()
    {
    	return {"something", "another thing"};
    }
    
    void function(string& first, string& second)
    {
    	first = "something";
    	second = "another thing";
    }
    
    int main()
    {
    	using clock = chrono::high_resolution_clock;
    
    	{
    		int j = 0;
    		auto start = clock::now();
    		for(int i = 0; i < 50000000; ++i)
    		{
    			auto v = function();
    			j += v.first[0] + v.second[0]; // Dummy calculation
    		}
    		auto end = clock::now();
    		chrono::duration<double> diff = end-start;
    		cout << j << " Time: " << diff.count() << endl;
    	}
    
    	{
    		int j = 0;
    		auto start = clock::now();
    		for(int i = 0; i < 50000000; ++i)
    		{
    			string first, second;
    			function(first, second);
    			j += first[0] + second[0];
    		}
    		auto end = clock::now();
    		chrono::duration<double> diff = end-start;
    		cout << j << " Time: " << diff.count() << endl;
    	}
    
    	{
    		int j = 0;
    		string first, second;
    		auto start = clock::now();
    		for(int i = 0; i < 50000000; ++i)
    		{
    			function(first, second);
    			j += first[0] + second[0];
    		}
    		auto end = clock::now();
    		chrono::duration<double> diff = end-start;
    		cout << j << " Time: " << diff.count() << endl;
    	}
    }
    

    Ergebnisse mit clang 3.5:

    2010065408 Time: 4.21777
    2010065408 Time: 4.34936
    2010065408 Time: 2.02025
    

    Und gcc 4.9:

    2010065408 Time: 4.26883
    2010065408 Time: 4.25236
    2010065408 Time: 1.57593
    

    Wie man sieht ist die Variante mit pair vergleichbar mit Referenzen, außer wenn man Objekte wieder benutzen kann.



  • Furble Wurble schrieb:

    Keine Sorge, ich glaube jeder baut seine ersten SQL-Statements händisch zusammen. Sicherer und gängiger ist es aber die sqlite3_bind_* Befehle zu benutzen. Insbesonders um sich gegen SQL-Injection zu verteidigen.

    Darüberhinaus würde ich auch gar nicht anfangen, einen String der übergeben wird auf bestimmtes Format zu parsen. Nimm doch eine überladene Funktion.

    #include <iostream>
    #include <string>
    #include <tuple>
    #include <sqlite3.h>
    
    using namespace std;
    
    sqlite3* db;
    
    tuple<string, string> get(int id){
      const char* sql = "SELECT name, value FROM test WHERE id=?";
      sqlite3_stmt *stmt;
      sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr);
      sqlite3_bind_int(stmt, 1, id);
      auto res=SQLITE_ROW==sqlite3_step(stmt) ?
        tuple<string, string>{ reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0)),
    			   reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1)) }
      : tuple<string, string>{};
      sqlite3_finalize(stmt);
      return res;
    }
    
    tuple<string, string> get(string name){
      const char* sql = "SELECT name, value FROM test WHERE name=?";
      sqlite3_stmt *stmt;
      sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr);
      sqlite3_bind_text(stmt, 1, name.c_str(), -1, nullptr);
      auto res=SQLITE_ROW==sqlite3_step(stmt) ?
        tuple<string, string>{ reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0)),
    			   reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1)) }
      : tuple<string, string>{};
      sqlite3_finalize(stmt);
      return res;
    }
    
    int main() {
      const char* sql = "CREATE TABLE test(id, name, value);"
        "INSERT INTO test VALUES"
        "(0, 'obj0', 'val0'), (1, 'obj1', 'val1'), (2, 'obj2', 'val2');";
      sqlite3_open(":memory:", &db);
      sqlite3_exec(db, sql, nullptr, nullptr, nullptr);
      string s1, s2;
      tie(s1, s2) = get(0);
      cout << s1 << ' ' << s2 << endl;
      tie(s1, s2) = get("obj2");
      cout << s1 << ' ' << s2 << endl;
      sqlite3_close(db);
    }
    

    Also das sind jetzt sowieso keine kritischen Daten, da brauche ich nicht zwingend Prepared Statements. Läuft alles lokal und der Benutzer hat da vollen Zugriff drauf.



  • geht ja nicht nur um sicherheit, sondern (insbesondere in C++) sind sie auch sauberer zu implementieren. Kann auch schneller sein, aber das kommt auf die Datenbank drauf an, bei mysql wird es wirklich etwas schneller.



  • Bennisen schrieb:

    Also das sind jetzt sowieso keine kritischen Daten, da brauche ich nicht zwingend Prepared Statements. Läuft alles lokal und der Benutzer hat da vollen Zugriff drauf.

    Du brauchst nicht zwingend Prepared Statements? Und warum nimmst Du sie dann nicht trotzdem? Sie sind in jeder hinsicht besser. Es gibt wirklich keinen Grund, sie nicht zu verwenden.

    Im übrigen ist das abschliessende Semikolon nicht richtig. Es wird von sqlite wohl toleriert, aber korrekt ist es nicht.

    Und ein *select ** sollte man auch nicht machen, sondern die Spalten aufzählen, die man selektiert. Ist sicherer und lesbarer. Also wieder in jeder hinsicht besser (ok - musst ein paar Zeichen mehr tippen).

    Und für die Rückgabe würde ich ein Datenobjekt oder struct definieren. Da bekommen die Member dann richtige Namen und es wird dann als ganzes zurück geliefert. Wenn man die Daten dann weiter verarbeitet, ist es ungleich lesbarer (und wieder sicherer) zu schreiben returnwert.vorname statt returnwert[0] .

    Und für die Optimierungsfuzzies hier ist eine solche Struktur sogar noch schneller als ein std::vector, da der std::vector die Daten zunächst dynamisch alloziieren muss, die Struktur aber nicht. Wobei das sowas von egal ist, wenn dei Daten aus einer Sqlite-Datenbank gelesen werden.



  • tntnet schrieb:

    Bennisen schrieb:

    Also das sind jetzt sowieso keine kritischen Daten, da brauche ich nicht zwingend Prepared Statements. Läuft alles lokal und der Benutzer hat da vollen Zugriff drauf.

    Du brauchst nicht zwingend Prepared Statements? Und warum nimmst Du sie dann nicht trotzdem? Sie sind in jeder hinsicht besser. Es gibt wirklich keinen Grund, sie nicht zu verwenden.

    Im übrigen ist das abschliessende Semikolon nicht richtig. Es wird von sqlite wohl toleriert, aber korrekt ist es nicht.

    Und ein *select ** sollte man auch nicht machen, sondern die Spalten aufzählen, die man selektiert. Ist sicherer und lesbarer. Also wieder in jeder hinsicht besser (ok - musst ein paar Zeichen mehr tippen).

    Und für die Rückgabe würde ich ein Datenobjekt oder struct definieren. Da bekommen die Member dann richtige Namen und es wird dann als ganzes zurück geliefert. Wenn man die Daten dann weiter verarbeitet, ist es ungleich lesbarer (und wieder sicherer) zu schreiben returnwert.vorname statt returnwert[0] .

    Und für die Optimierungsfuzzies hier ist eine solche Struktur sogar noch schneller als ein std::vector, da der std::vector die Daten zunächst dynamisch alloziieren muss, die Struktur aber nicht. Wobei das sowas von egal ist, wenn dei Daten aus einer Sqlite-Datenbank gelesen werden.

    Wenn man so weit geht, nutzt man am besten noch ein ORM, ist zwar langsamer, aber verdammt produktiv und umgeht alles diese Probleme. Gibt bestimmt auch ein paar gute für C++


Anmelden zum Antworten