Code für Ausgabe vereinfachen.



  • Du könntest ein template schreiben:

    template<typename T>
    std::string str(T typ)
    {
        stringstream str;
        //Umwandeln ...
    }
    

    Und dies deiner Funktion mit Variabler Anzahl von parametern übergeben:

    print(int Anzahl, .... )
    {
       //Hier wie printf arbeiten, nur dass der Typ der Argumente (string) bekannt ist
    }
    
    //So dann benutzen:
    print( str(3.0), "Hallo", str(123) );
    

    Wie wär das? 😕

    //Edit: Audacia macht es natürlich sehr viel schöner. 😞



  • print0r schrieb:

    Ich portiere mein Projekt von C nach C++, in C habe ich die Aufgabe so gelöst:

    char msgbuf[1024];
    char* msg_mk ( char* a, ... )
    {
    	va_list v;
    	va_start(v,a);
    	vsnprintf ( msgbuf, sizeof(msgbuf) / sizeof(*msgbuf) -1, a, v );
    	va_end(v); 
    	return msgbuf;
    }
    print( msg_mk ( "%s%d%s%d%s\n", "Cr", 3, "at", 0, "r" ));
    

    Ich dachte mir, von der Codeästhetik her betrachtet, wäre es besser von char* auf string zu wechseln.

    Sicherlich, aber dann verwende doch so etwas, anstatt dich mit Streams herumzuquälen:

    std::string str_printf (const char* format, ...)
    {
        std::va_list args;
        std::string retval;
    
        va_start (args, format);
        retval.resize (std::vsnprintf (0, 0, format, args));
        std::vsnprintf (&retval[0], retval.size () + 1, format, args);
        va_end (args);
    
        return retval;
    }
    


  • ... oder so:

    #include <iostream>
    #include <string>
    #include <sstream>
    
    /// anything to string
    class a2str
    {
      std::stringstream ss;
    public:
      template<typename T>
      a2str& operator<<(T const& x) { ss<<x; return *this; }
      operator std::string() const { return ss.str(); }
    };
    
    int main()
    {
      int i = 23;
      std::string s = a2str() << "mein i ist " << i << ".";
      std::cout << s << '\n';
    }
    

    Gruß,
    SP



  • Pigeon schrieb:

    Du könntest ein template schreiben [...] Und dies deiner Funktion mit Variabler Anzahl von parametern übergeben:

    template<typename T>
    std::string str(T typ);
    
    // ...
    
    print(int Anzahl, ... )
    {
       // Hier wie printf arbeiten, nur dass der Typ der Argumente
       // (string) bekannt ist
    }
    

    Das mit den variadischen Funktionen funktioniert nur für PODs (plain old data structures). std::string ist kein POD. Dieses Sprachmerkmal ("...") ist sooo unattraktiv, dass ich gar nicht weiß, wie man es richtig benutzen müsste. :p

    Gruß,
    SP



  • Sebastian Pizer schrieb:

    Das mit den variadischen Funktionen funktioniert nur für PODs (plain old data structures). std::string ist kein POD. Dieses Sprachmerkmal ("...") ist sooo unattraktiv, dass ich gar nicht weiß, wie man es richtig benutzen müsste. :p

    Gruß,
    SP

    😞 Wieder was gelernt. 😞



  • Pigeon schrieb:

    //Edit: Audacia macht es natürlich sehr viel schöner. 😞

    Mach dir nix draus, auf den Code von Audacia wäre ich auch in meinen kühnsten Träumen nicht gekommen!
    Vor allem nicht auf die Zeilen:

    retval.resize (vsnprintf (0, 0, format, args)); 
    	vsnprintf (&retval[0], retval.size () + 1, format, args);  :open_mouth:
    

    @Audacia:

    cout << str_printf ( "%s%d%s\n", "str_printf ist ", 1, "A!" );
    

    🙂

    @Sebastian Pizer
    Ebenfalls ne 1A Variante, die ich (noch nicht ganz) kapiere aber die ich so benutzen kann:

    cout << (s = a2str() << "Meine " << 6 << " Frauen sind super!")
    ;
    

    🙂

    Pigeon schrieb:

    😞 Wieder was gelernt. 😞

    Warum machst du so ein Gesicht? Freu dich doch, das du dazugelernt hast. 😉
    Das nächste mal bist du derjenige, der die beste Lösung vorschlägt.
    Würde ich jedes mal so ein Gesicht machen, wenn ich etwas dazulerne, wäre mein Kinn schon blutig gescheuert vom auf dem Boden schleifen. :p

    So, ich muss jetzt erstmal leider Feierabend machen und ab gehts zum Baden in die Sonne!
    Vielen Dank allen beteiligten! 👍
    🙂



  • Sebastian Pizer schrieb:

    Das mit den variadischen Funktionen funktioniert nur für PODs (plain old data structures). std::string ist kein POD. Dieses Sprachmerkmal ("...") ist sooo unattraktiv, dass ich gar nicht weiß, wie man es richtig benutzen müsste. :p

    Da muss ich mich anschliessen. Mir gefällt aber audacias Code auch sonst nicht besonders... 😉

    Was spricht gegen eine Klasse, die den operator<< anbietet und dessen Aufrufe an die Systemfunktionen weiterleitet?

    struct endl_t {} endl; // führt zum Weiterleiten ans System
    
    class streamer
    {
        public:
            template <typename T>
            streamer& operator<< (const T& value)
            {
                m_stream << value;
                return *this;
            }
    
            streamer& operator<< (const endl_t&)
            {
                system_output(m_stream.str());
                m_stream.str("");
                return *this;
            }
    
        private:
            void system_output(const std::string& message)
            {
                // tue je nach System etwas anderes
            }
    
        private:
            std::stringstream m_stream;
    };
    

    Anwendung:

    streamer s;
    s << "Hallo" << 43 << endl;
    


  • Nexus schrieb:

    Mir gefällt aber audacias Code auch sonst nicht besonders... 😉

    Wenn du etwas konkreter wärest, könnte man fast meinen, daß du auch einen Grund dafür hättest.



  • Muss man das noch begründen?

    Ok, wie wär's mit:
    - VarArgs sind typunsicher
    - VarArgs funktionieren nur für PODs
    - VarArgs erlauben dir hier nicht, beliebige benutzerdefinierte Typen T zu unterstützen, für die es einen operator<<(ostream&, const T&) gibt.



  • audacia schrieb:

    Nexus schrieb:

    Mir gefällt aber audacias Code auch sonst nicht besonders... 😉

    Wenn du etwas konkreter wärest, könnte man fast meinen, daß du auch einen Grund dafür hättest.

    Der Code ist sicherlich gute für seinen bestimmten Anwendungszweck, lässt für den "modernen" Einsatz aber vermissen, eigene Objekte zu verwenden, bei denen z.B. ostream& operator<<( ostream& o, const T& elem ) überladen ist.

    Den Anwendungsfall des OP gibt es ja in mehreren Projekten, da ziehe ich eigentlich immer einen eigenen streambuf für cout/cerr vor, der bei einem flush (also auch endl) den Text überträgt/behandelt. Das eignet sich nur leider nicht für Internationalisierung à la gettext.
    Internationalisierung hatte ich sowieso erst in einem einzigen Projekt (und dann noch in einem privaten), da hatte ich das etwa so gelöst:

    // Im Code:
    std::string error_text = makeError( "BlaNotFound", obj1, obj2, obj3, ... );
    
    // In der Sprach-Datei (.txt-Datei):
    BlaNotFound: Object %1 could not be found in area %3 where %2=%4
    ...
    

    "makeError" hat die Objekte dann per stringstream in Strings umgewandelt und in den Sprach-String eingefügt.

    edit: Alter, über 5 Minuten für einen Post 😕



  • Sebastian Pizer schrieb:

    Muss man das noch begründen?

    Daß "echte C++-Programmierer" variadische Funktionen als Teufelszeug empfinden, ist mir bekannt. Interessiert wäre ich eher an einer Erklärung des "auch sonst".

    Sebastian Pizer schrieb:

    - VarArgs sind typunsicher

    Es ist aber problemlos möglich, die Funktionen z.B. im Debug-Build durch typsichere zu ersetzen.

    Sebastian Pizer schrieb:

    - VarArgs funktionieren nur für PODs
    - VarArgs erlauben dir hier nicht, beliebige benutzerdefinierte Typen T zu unterstützen, für die es einen operator<<(ostream&, const T&) gibt.

    Das sind die klassischen Argumente für C++-Streams. Nur sind C++-Streams trotz aller Vorteile in der Theorie IMHO die schlechteste aller Alternativen; mit z.B. boost::format gibt es deutlich sinvollere.

    Es hat schon einen Grund, daß keine Sprache außer C++ sich auf so etwas abstruses wie diese Streams einläßt. In allen vernünftigen Sprachen existiert printf() oder eine vergleichbare Alternative.

    Badestrand schrieb:

    Der Code ist sicherlich gute für seinen bestimmten Anwendungszweck

    Eben. Darum ging es.



  • audacia schrieb:

    Daß "echte C++-Programmierer" variadische Funktionen als Teufelszeug empfinden, ist mir bekannt. Interessiert wäre ich eher an einer Erklärung des "auch sonst".

    Ich sagte extra "mir gefällt" und nicht etwa "audacias Code ist", es handelt sich also um eine subjektive Ansicht. Ich bin nicht unbedingt ein Fan von den C-Formatfunktionen und VarArgs. Aber auch das finde ich beispielsweise nicht allzu toll, da du hier davon ausgehst, dass der std::string an einem Stück sein muss, was nicht garantiert ist:

    &retval[0]
    

    Und std::vsnprintf ist nicht wirklich im Standard, sondern eher eine sicherere Version von std::vsprintf , oder?

    audacia schrieb:

    Nur sind C++-Streams trotz aller Vorteile in der Theorie IMHO die schlechteste aller Alternativen; [...]

    Es hat schon einen Grund, daß keine Sprache außer C++ sich auf so etwas abstruses wie diese Streams einläßt. In allen vernünftigen Sprachen existiert printf() oder eine vergleichbare Alternative.

    Ja, so langsam wissen wir, dass dir die C++-Streams nicht gefallen. Du musst sie trotzdem nicht so darstellen, als ob man kaum mit ihnen arbeiten könnte, als ob sie die reinste Hölle wären. Sie sind vielleicht nicht der Höhepunkt der Ein-/Ausgabeprogrammierung, aber als so schlimm empfinde ich sie bei weitem nicht. Ich persönlich finde es sogar angenehmer, mit überladenem operator<< zu arbeiten. Mir sind Typsicherheit und leichte Erweiterbarkeit für eigene Typen auch wichtig. Klar sehe ich auch ein, dass die Streams einige Nachteile haben, aber diese sind momentan nicht derart bedrückend, dass ich lieber auf dem C-Level arbeite. Das ist natürlich Geschmackssache und hängt von den jeweiligen Anforderungen ab...



  • Nexus schrieb:

    Aber auch das finde ich beispielsweise nicht allzu toll, da du hier davon ausgehst, dass der std::string an einem Stück sein muss, was nicht garantiert ist

    ISO/IEC 14882 schrieb:

    §21.3.4 basic_string element access
    `const_reference operator[](size_type pos) const;

    reference operator[](size_type pos);Returns: Ifpos < size(), returnsdata()[pos]. Otherwise, ifpos == size(), the const version returnscharT()` . Otherwise, the behavior is undefined.

    Da basic_string::data() einen Zeiger zurückgibt, impliziert das, daß die Daten linear vorliegen. Es sei natürlich denn, ich übersehe etwas.

    Nexus schrieb:

    Und std::vsnprintf ist nicht wirklich im Standard, sondern eher eine sicherere Version von std::vsprintf , oder?

    Soweit ich weiß, ist sie Teil von C99, und zwar ein Teil, den fast alle C++-Compiler implementieren.

    Nexus schrieb:

    Ja, so langsam wissen wir, dass dir die C++-Streams nicht gefallen. Du musst sie trotzdem nicht so darstellen, als ob man kaum mit ihnen arbeiten könnte, als ob sie die reinste Hölle wären.

    Du beliebst maßlos zu übertreiben. Ich bemängele lediglich, daß sie stets als unumgänglich bessere Alternative dargestellt werden.

    Nexus schrieb:

    Das ist natürlich Geschmackssache und hängt von den jeweiligen Anforderungen ab...

    So ist es. Die Anforderung war hier die einfache Portierung von C-Code.


  • Administrator

    audacia schrieb:

    Daß "echte C++-Programmierer" variadische Funktionen als Teufelszeug empfinden, ist mir bekannt. Interessiert wäre ich eher an einer Erklärung des "auch sonst".

    Ich bin doch ein "echter C++ Programmierer", zumindest schimpft man mich immer wieder als viel zu C++ treu, und ich könnte nicht ohne variadische Funktionen leben!
    Wieso? Na deswegen:

    typedef char False;
    typedef struct { False dummy[2]; } True;
    
    template<typename FromT, typename ToT>
    struct IsConvertible
    {
    private:
      True test(ToT const&);
      False test(...); // <---------
      FromT make();
    
    public:
      static bool const RESULT = sizeof(True) == sizeof(test(make()));
      static bool const IS_SAME = false;
    };
    
    template<typename SameT>
    struct IsConvertible<SameT, SameT>
    {
      static bool const RESULT = true;
      static bool const IS_SAME = true;
    };
    

    😃

    Für etwas anderes, würde ich allerdings echt keine variadische Funktionen nehmen, also um solche Funktionen richtig aufzurufen. Ist mir viel zu unsicher. Ich habe sie ein paar Male verwenden wollen und immer ist mir irgendwo ein Fehler unterlaufen. Fängt bei kleinen Dingen an, wie zum Beispiel:

    void fooFloats(std::size_t count, ...)
    {
      va_list list;
      va_start(list, count);
    
      for(std::size_t a = 0; a < count; ++a)
      {
        float f = va_arg(list, float);
        // mach was mit f.
      }
    
      va_end(list);
    }
    
    // ...
    fooFloats(5, 3.2, 234.0, 23, 23.0);
    // Alle Argumente sind falsch.
    

    Falsche Anzahl und (double, double, int, double), statt (float, float, float, float). Ein Fehler passiert einfach viel zu schnell. Aber wer mit dem Risiko leben kann ...

    Da würde ich es eher über Proxies lösen, wie es auch Boost.Format gemacht hat.
    Variadische Funktionen werde ich erst mit dem nächsten Standard anfangen zu verwenden, da man diese dann über Variadic Templates typsicher machen kann.

    audacia schrieb:

    Da basic_string::data() einen Zeiger zurückgibt, impliziert das, daß die Daten linear vorliegen. Es sei natürlich denn, ich übersehe etwas.

    Die Daten liegen zwar linear in diesem Pointer, aber wohin der Pointer zeigt, ist nicht definiert. Grundsätzlich darf hier ein neues Array angelegt werden, welches linear ist, während die interne Repräsentation nicht linear ist.

    Dazu kannst du zum Beispiel dies hier lesen:

    References, pointers, and iterators referring to the elements of a basic_string sequence may be invalidated by the following uses of that basic_string object:
    — As an argument to non-member functions swap() (21.3.7.8), operator>>() (21.3.7.9), and getline() (21.3.7.9).
    — As an argument to basic_string::swap().
    — Calling data() and c_str() member functions.
    — Calling non-const member functions, except operator[](), at(), begin(), rbegin(), end(), and rend().
    — Subsequent to any of the above uses except the forms of insert() and erase() which return iterators, the first call to non-const member functions operator[](), at(), begin(), rbegin(), end(), or rend().

    Der zurückgegeben Pointer kann also invalidiert werden, wenn man erneut data aufruft. Also kann es sich um einen anderen Pointer handeln. Ich kenne zwar keine Standardbibliothek, welche dies wirklich so anwendet. Im nächsten Standard wird dies glaub ich sogar korrigiert.

    Grüssli



  • Dravere schrieb:

    audacia schrieb:

    Da basic_string::data() einen Zeiger zurückgibt, impliziert das, daß die Daten linear vorliegen. Es sei natürlich denn, ich übersehe etwas.

    Die Daten liegen zwar linear in diesem Pointer, aber wohin der Pointer zeigt, ist nicht definiert. Grundsätzlich darf hier ein neues Array angelegt werden, welches linear ist, während die interne Repräsentation nicht linear ist.

    Du vergißt, daß ich den Wert der darunterliegenden Zeichens verändern darf. Eine Implementation, bei der meine obige Annahme nicht zutrifft, müßte also so aussehen:

    template <typename CharT>
        class basic_string
    {
    private:
        CharT* actualData;
        CharT* consecutiveData;
        int lastRequestedChar;
    
        void applyCharModifications (void)
        {
            if (lastRequestedChar >= 0)
            {
                actualData[lastRequestedChar] = consecutiveData[lastRequestedChar];
                lastRequestedChar = -1;
            }
        }
    public:
        const CharT* data (void) const
        {
            applyCharModifications ();
            delete consecutiveData;
            consecutiveData = new CharT[size () + 1];
            memcpy (consecutiveData, actualData, sizeof (CharT) * (size() + 1));
            return consecutiveData;
        }
        CharT& operator [] (unsigned idx)
        {
            applyCharModifications ();
            return const_cast <CharT*> (data ())[idx];
        }
        ...
    };
    

    Dravere schrieb:

    Dazu kannst du zum Beispiel dies hier lesen:

    References, pointers, and iterators referring to the elements of a basic_string sequence may be invalidated by the following uses of that basic_string object:
    — As an argument to non-member functions swap() (21.3.7.8), operator>>() (21.3.7.9), and getline() (21.3.7.9).
    — As an argument to basic_string::swap().
    — Calling data() and c_str() member functions.
    — Calling non-const member functions, except operator[](), at(), begin(), rbegin(), end(), and rend().
    — Subsequent to any of the above uses except the forms of insert() and erase() which return iterators, the first call to non-const member functions operator[](), at(), begin(), rbegin(), end(), or rend().

    Wenn ich das recht verstehe, sagt dieser Abschnitt ausdrücklich, daß folgendes zutrifft:

    CharT& c1 = theString[0]; // first call to non-const member function operator [] ()
    theString[1]; // second call - c1 bleibt gültig!
    CharT& c2 = theString[0]; // da c1 fürderhin gültig bleibt, muß &c1 == &c2 sein
    

    Zwar scheint die von dir hervorgehobene Stelle dagegen zu sprechen, weil der Rückgabewert von operator [] () über data() definiert ist, doch mir scheint die explizite Erwähnung des Indexoperators in der Auflistung von höherem Gewicht zu sein, und die postuliert nur beim erstmaligen Aufruf eine Invalidierung vorheriger Referenzen.

    Es ist richtig, daß die Bedingung (&(operator [] (n)) + 1 == &(operator [] (n + 1)) nicht erfüllt sein muß, aber die von dir zitierte Stelle macht selbst meinen obigen Versuch, diesen Fall herbeizuführen, nichtig. Um also unter Erfüllung der zusätzlichen Forderung meine Annahme zu widerlegen, müßte std::basic_string<> für mit verschiedenen Indizes durchgeführte operator [] () -Aufrufe Zeiger auf unterschiedliche Strings zurückgeben, dabei aber beim Aufruf mit demselben Index jedesmal dieselbe Referenz, und sobald z.B. data() aufgerufen wird, sämtliche Änderungen wieder zum tatsächlichen String zusammenführen. Und das ist, da sind wir uns sicherlich einig, dermaßen abstrus, daß man es getrost außer acht lassen kann 😉

    Dravere schrieb:

    Im nächsten Standard wird dies glaub ich sogar korrigiert.

    Hoffentlich. Für std::vector<> korrigierte das ja, wenn ich mich recht erinnere, bereits der 2003er-Standard.


  • Administrator

    @audacia,
    Du verstehst mich wohl falsch oder ich dich. Ich wollte nur sagen, dass der zurückgelieferte Zeiger von data , nicht auf das interne Array verweisen muss. Daher kannst du über die linearen Daten, welche sich an der Adresse des von data zurückgelieferten Zeigers liegen, nicht darauf schliessen, dass sie intern auch linear angelegt sind. Es wäre möglich, dass sie im basic_string als Deque gespeichert sind.

    template<typename CharT, /* ... */ >
    class basic_string
    {
      std::deque<CharT> m_characters;
    
      // ...
    
    public:
      CharT const* data() const
      {
        static std::vector<CharT> buffer;
        buffer.resize(m_characters.size());
        std::copy(m_characters.begin(), m_characters.end(), buffer.begin());
    
        return &buffer[0];
      }
    
      // ...
    
      CharT& operator [](std::size_t index)
      {
        return m_characters[index]; // entspricht durchaus data()[index]
      }
    
      // ...
    };
    
    // Wenn du nun zum Beispiel sowas machst, dann hast du garantiertes undefiniertes Verhalten:
    std::string mystring = "test";
    std::fill_n(&mystring[0], 4, 0); // Fehlannahme, dass der Zeiger auf ein zusammenhängendes Array verweist.
    

    Grüssli



  • Dravere schrieb:

    @audacia,
    Du verstehst mich wohl falsch oder ich dich.

    Das befürchte ich.

    Dravere schrieb:

    Ich wollte nur sagen, dass der zurückgelieferte Zeiger von data , nicht auf das interne Array verweisen muss. Daher kannst du über die linearen Daten, welche sich an der Adresse des von data zurückgelieferten Zeigers liegen, nicht darauf schliessen, dass sie intern auch linear angelegt sind. Es wäre möglich, dass sie im basic_string als Deque gespeichert sind.

    Das trifft zu, ist aber vollkommen irrelevant für meine obige Annahme.

    Dravere schrieb:

    // Wenn du nun zum Beispiel sowas machst, dann hast du garantiertes undefiniertes Verhalten:
    std::string mystring = "test";
    std::fill_n(&mystring[0], 4, 0); // Fehlannahme, dass der Zeiger auf ein zusammenhängendes Array verweist.
    

    Nein, das ist keine Fehlannahme, denn dem Standard zufolge zeigt data() , über das der operator [] () definiert ist, auf ein zusammenhängendes Array:

    ISO/IEC 14882 schrieb:

    21.3.6 basic_stringstring operations
    const charT* data() const;
    3 Returns: If size() is nonzero, the member returns a pointer to the initial element of an array whose first
    size() elements equal the corresponding elements of the string controlled by *this. If size() is
    zero, the member returns a non-null pointer that is copyable and can have zero added to it.

    Was der Standard nicht vorschreibt, ist, daß ich mittels (&operator [] (n))[1] das Zeichen unter operator [] (n + 1) verändern kann. Aber hast du mal darüber nachgedacht, wie eine Implementation aussehen müßte, bei der das tatsächlich nicht der Fall ist? Falls nein, lies meinen obigen Post nocheinmal, da habe ich es ausgeführt.



  • audacia schrieb:

    Du beliebst maßlos zu übertreiben. Ich bemängele lediglich, daß sie stets als unumgänglich bessere Alternative dargestellt werden.

    Okay, das war etwas überspitzt formuliert. Mir ist jedoch aufgefallen, dass du die C++-Streams sehr oft schlecht machst - meiner Ansicht nach schlechter, als sie sind. Abgesehen davon ist die Aussage, Streams würden als unumgänglich bessere Alternative dargestellt, genauso übertrieben. Sie werden den Anfängern vielleicht als erste Möglichkeit vorgeschlagen, was auch seinen Grund hat. Etliche Fehlerquellen der C-Funktionen werden durch die Streams eingeschränkt oder ganz aus der Welt geschaffen, was einem Neuling sicher sehr entgegen kommt. Hingegen kann man als Anfänger die Nachteile der Streams eher leicht verkraften.

    audacia schrieb:

    Was der Standard nicht vorschreibt, ist, daß ich mittels (&operator [] (n))[1] das Zeichen unter operator [] (n + 1) verändern kann. Aber hast du mal darüber nachgedacht, wie eine Implementation aussehen müßte, bei der das tatsächlich nicht der Fall ist? Falls nein, lies meinen obigen Post nocheinmal, da habe ich es ausgeführt.

    Klar werden es vernünftige Implementierungen nicht so lösen - zum Beispiel würden diese für c_str() auch nicht ein neues Array mit '\0' erstellen.

    Scheint schon so, als wäre diese Annahme in den meisten Fällen gewährleistet, darauf verlassen würde ich mich trotzdem nicht. Zum Beispiel muss data() auch keinen nullterminierten String zurückgeben. Oder kann Nullen innerhalb des Strings zurückgeben, die von den C-Funktionen als Terminierung angesehen werden.


  • Administrator

    audacia schrieb:

    Nein, das ist keine Fehlannahme, denn dem Standard zufolge zeigt data() , über das der operator [] () definiert ist, auf ein zusammenhängendes Array:

    audacia schrieb:

    Was der Standard nicht vorschreibt, ist, daß ich mittels (&operator [] (n))[1] das Zeichen unter operator [] (n + 1) verändern kann.

    Die beiden Sätze sind meiner Meinung nach der komplette Widerspruch. operator [](index) ist nur über data()[index] definiert, also dass hier der gleiche Wert zurückkommt. Es ist nicht definiert, dass der Zeiger auf das zurückgegebene Element gleich sein muss. Daher ist dies undefiniertes Verhalten:

    std::fill_n(&mystring[0], 4, 0);
    

    Weil man hier eben davon ausgeht, dass man mittels (&operator [] (n))[1] das Zeichen unter operator [] (n + 1) verändern kann. Was du selber sagst, dass es nicht vom Standard vorgeschrieben ist ...

    audacia schrieb:

    Aber hast du mal darüber nachgedacht, wie eine Implementation aussehen müßte, bei der das tatsächlich nicht der Fall ist?

    Ja, ich bin sogar schon mal soweit gegangen, dass ich dies selber getestet habe. Es gibt durchaus eine gewisse Berechtigung für sowas. Wenn man nämlich Einfügeoperationen im std::string beschleunigen möchte, also nicht nur das Anfügen am Ende, dann kann man hier durch eine veränderte Speicherverwaltung eine gewisse Optimierung erzielen. Man kann zum Beispiel auch davon ausgehen, dass c_str und data Methoden sind, welche eher seltener aufgerufen werden und daher ruhig etwas teuerer sein dürfen.
    Also ganz so abwegig finde ich eine solche Implementation nicht.

    Aber es ist wohl wie bei der Verwendung von variadischen Funktionen, wenn man das Risiko eingehen möchte, wobei man hier sogar nicht standardkonforme Operationen durchführt ... Du bist wohl einfach risikofreudiger als andere 😉

    Grüssli



  • Dravere schrieb:

    Die beiden Sätze sind meiner Meinung nach der komplette Widerspruch. operator [](index) ist nur über data()[index] definiert, also dass hier der gleiche Wert zurückkommt.

    operator [] () gibt eine Referenz zurück, über die du den Wert ändern kannst. Das macht den Unterschied aus.

    Dravere schrieb:

    audacia schrieb:

    Aber hast du mal darüber nachgedacht, wie eine Implementation aussehen müßte, bei der das tatsächlich nicht der Fall ist?

    Ja, ich bin sogar schon mal soweit gegangen, dass ich dies selber getestet habe. Es gibt durchaus eine gewisse Berechtigung für sowas. Wenn man nämlich Einfügeoperationen im std::string beschleunigen möchte, also nicht nur das Anfügen am Ende, dann kann man hier durch eine veränderte Speicherverwaltung eine gewisse Optimierung erzielen. Man kann zum Beispiel auch davon ausgehen, dass c_str und data Methoden sind, welche eher seltener aufgerufen werden und daher ruhig etwas teuerer sein dürfen.
    Also ganz so abwegig finde ich eine solche Implementation nicht.

    Na, dann zeig mal her. Davon ausgehend, daß die Motivation für deine Implementation "nicht ganz abwegig" ist, behaupte ich: entweder ist deine Implementation nicht standardkonform, oder meine obige Annahme trifft zu.

    Dravere schrieb:

    Aber es ist wohl wie bei der Verwendung von variadischen Funktionen, wenn man das Risiko eingehen möchte

    Keine Sorge, bei mir sind printf() und dergleichen allesamt typsicher 😉


Anmelden zum Antworten