Code für Ausgabe vereinfachen.



  • 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 😉


  • Administrator

    audacia schrieb:

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

    Aber nur der Wert an dieser Stelle. Du kannst nicht vom referenzierten Wert den Zeiger holen, diesen inkrementieren und dann die Stelle beschreiben oder lesen. Das ist undefiniertes Verhalten. Aber genau das macht der präsentierte Code von dir oder der von mir mit std::fill_n .

    audacia schrieb:

    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.

    Würde ich gerne tun, hätte ich bereits oben schon getan, wenn ich denn könnte. Es ist schon ein wenig her, als ich das gemacht habe, und in der Zwischenzeit gab es einen Zwischenfall mit einem defekten S-ATA Kabel, welcher leider unteranderem diesen Code zerstört hat. 😞

    Da ich es nur zu Testzwecken erstellt hatte und es nicht für wichtig hielt, gibt es davon auch kene Backups und habe bis heute keine Lust empfunden, dies nochmals zu testen, bzw. zu implementieren. Auch jetzt nicht 😉

    Grüssli



  • audacia schrieb:

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

    Welche Typsicherheit meinst Du denn? Die Sorte von Typsicherheit, die Typfehler schon zur Kompilierzeit abfangen kann oder nur die Sorte, die Laufzeitüberprüfungen einbaut?

    Gruß,
    SP



  • so was in der art?!

    #include <algorithm>
    #include <deque>
    #include <memory>
    #include <string>
    #include <vector>
    
    template
    <
    	typename TChar,
    	typename TTraits = std::char_traits <TChar>,
    	typename TAlloc = std::allocator<TChar>
    >
    struct my_string
    {
    public:
    	typedef				TTraits								traits_type;
    	typedef typename	traits_type::char_type				value_type;
    	typedef				TAlloc								allocator_type;
    	typedef typename	allocator_type::size_type			size_type;
    
    	typedef typename	allocator_type::const_reference		const_reference;
    	typedef typename	allocator_type::reference			reference;
    	typedef typename	allocator_type::const_pointer		const_pointer;
    	typedef typename	allocator_type::pointer				pointer;
    private:
    	typedef std::deque<value_type, allocator_type>			saving_container;
    	typedef std::vector<value_type, allocator_type>			c_string_container;
    
    	saving_container		string_data;
    public:
    	typedef typename	saving_container::const_iterator	const_iterator;
    	typedef typename	saving_container::iterator			iterator;
    public:
    	my_string(const_pointer _string)
    	: string_data( _string, _string+traits_type::length(_string) )
    	{}
    
    	const_iterator begin() const
    	{
    		return string_data.begin();
    	}
    
    	iterator begin()
    	{
    		return string_data.begin();
    	}
    
    	const_iterator end() const
    	{
    		return string_data.end();
    	}
    
    	iterator end()
    	{
    		return string_data.end();
    	}
    
    	const_pointer c_str() const
    	{
    		return data();
    	}
    
    	const_pointer data() const
    	{
    		static c_string_container c_array;
    		c_array.resize(size()+1);
    		std::copy(string_data.begin(), string_data.end(), c_array.begin());
    		return &*c_array.begin();
    	}
    
    	const_reference operator []( size_type pos ) const
    	{
    		return string_data[pos];
    	}
    	reference operator []( size_type pos )
    	{
    		return string_data[pos];
    	}
    
    	size_type size() const
    	{
    		return string_data.size();
    	}
    
    	my_string& operator+= (const my_string &rhs)
    	{
    		string_data.insert( string_data.end(), rhs.string_data.begin(), rhs.string_data.end() );
    		return *this;
    	}
    };
    typedef my_string<char> string;
    
    #include <algorithm>
    #include <iostream>
    
    int main()
    {
    	string a = "asdasdasdasdasdasdasdadsasdasd";
    	a += "qweqweqweqweqweqweqweqweqweqweqweqweqwe";
    	a += "yxcyxcyxcyxcyxcyxcyxcyxcyxcyxcyxcyxcyxc";
    	a[a.size()-1] = 'f';
    	std::cout << a.c_str() << std::endl;
    //	std::fill_n(&a[0], a.size(), 'A');
    	std::cout << a.c_str() << std::endl;
    
    	system("PAUSE");
    }
    

    ich nehm mal an, dass ich mich an alles gehalten habe, was im standard steht ^^
    vollständig ist es natürlich nicht - aber ich denke, es verdeutlicht das, was dravere geschrieben hat...

    bb



  • Dravere schrieb:

    audacia schrieb:

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

    Aber nur der Wert an dieser Stelle. Du kannst nicht vom referenzierten Wert den Zeiger holen, diesen inkrementieren und dann die Stelle beschreiben oder lesen. Das ist undefiniertes Verhalten. Aber genau das macht der präsentierte Code von dir oder der von mir mit std::fill_n .

    Das ist mir schon klar. Aber der Rückgabewert des Indexoperators muß praktisch *const_cast <CharT*> (data () + n) sein - wie von der Definition des Index-Operators gefordert -, und data() gibt einen Zeiger auf den Datenbestand des Strings in Form eines Arrays zurück. Der Lesezugriff auf const_cast <CharT*> (data () + n)[1] ist daher legal; der Schreibzugriff zwar nicht, aber in der Praxis existiert keine Prozessorarchitektur, die eine Page-Granularität einzelner Bytes hat, so daß der Schreibzugriff auch für const_cast <CharT*> (data () + n)[1] funktionieren wird. Jedoch nicht garantiert ist, daß eine Änderung auf diesem Wege auch den tatsächlichen String ändert - aber wie ich oben schrieb, wäre eine Implementation, die das ausnutzt, am Rande des Wahnsinns.



  • Sebastian Pizer schrieb:

    Welche Typsicherheit meinst Du denn? Die Sorte von Typsicherheit, die Typfehler schon zur Kompilierzeit abfangen kann oder nur die Sorte, die Laufzeitüberprüfungen einbaut?

    Laufzeitüberprüfung. Die Überprüfung zur Übersetzungszeit ist nicht möglich, wenn der Format-String erst zur Laufzeit bestimmt wird (z.B. mit gettext). Für die Fälle, wo es aber möglich ist, gibt es z.B. -Wformat bei GCC.

    unskilled schrieb:

    ich nehm mal an, dass ich mich an alles gehalten habe, was im standard steht ^^

    Hast du nicht - dein Indexoperator gibt nicht data()[n] zurück, wie vom Standard gefordert.



  • Aber der Rückgabewert des Indexoperators muß praktisch *const_cast <CharT*> (data () + n) sein

    wieso sollte er?

    standard, 21.3.4 basic_string element access schrieb:

    Returns: If pos < size(), returns *(begin() + pos ). Otherwise, if pos == size(), the const version returns charT(). Otherwise, the behavior is undefined.

    das heißt, mein bsp. is doch nich ganz standard-konform, weil ich bei pos==size keine charT() garantieren kann, aber dann muss ich eben doch die 0 mitspeichern und es ist wieder standardkonform

    bb

    edits: falls es doch iwo steht, dann such ma pls die stelle raus, wo das steht...
    das war der working draft, wo das so drin steht ^^
    hab ma noch nen älteren standard (wieder nur nen draft-.-) gefunden(eher uralt, weil von 1996 - aber nen besseren hab ich nich gefunden)
    da steht das drin:

    1 Effects: The reference returned is invalid after any subsequent call to c_str(), data(), or any nonconst member function for the object.
    2 Returns: If pos < size(), returns data()[pos]. Otherwise, if pos == size(), the const version returns traits::eos(). Otherwise, the behavior is undefined.

    wenn das noch gültig ist, dann hast du natürlich recht ^^ aber dann kann ich einfach sagen, dass ich nach dem kommenden standard standardkonform wäre 😉



  • unskilled schrieb:

    edits: falls es doch iwo steht, dann such ma pls die stelle raus, wo das steht...
    das war der working draft, wo das so drin steht ^^
    hab ma noch nen älteren standard (wieder nur nen draft-.-) gefunden(eher uralt, weil von 1996 - aber nen besseren hab ich nich gefunden)

    Hättest du den Thread gelesen, so wäre dir vielleicht aufgefallen, daß ich die entsprechende Stelle oben bereits zitiert habe, und zwar aus dem 2003er-Standard.

    audacia schrieb:

    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.



  • stimmt 😉

    hab ich ma wieder nur die hälfte gelesen...
    dann fällt mir jetzt gerade auch keine möglichkeit ein - wirst du wohl recht haben ^^
    Imho ist returns data()[pos] eigtl schon die Garantie dafür, dass intern ein vector genommen wird...
    aber in nem Jahr ist das dann hoffentlich nicht mehr so ^^

    bb


Anmelden zum Antworten