Korrekter Aufruf von 'swap'



  • Hi,

    Scott Meyers zeigt, wie man swap korrekt aufruft, nämlich folgendermaßen:

    T x, y;
    using std::swap;
    swap(x, y);
    

    Gewappnet mit dieser Kenntnis wollte ich jetzt für eine Klasse die korrekte Swap-Semantik implementieren, strikt nach Item 25 aus „Effective C++“ (3rd ed.). Leider klappt das nicht. Folgender Code:

    #include <iostream>
    #include <functional>
    #include <algorithm>
    
    template <typename T, typename F>
    class unmanaged_resource {
        private:
            T m_value;
            F m_deleter;
    
        public:
            unmanaged_resource(T const& value)
                : m_deleter(), m_value(value) { }
    
            ~unmanaged_resource() {
                m_deleter(m_value);
            }
    
        private:
            unmanaged_resource(unmanaged_resource const&);
            unmanaged_resource& operator =(unmanaged_resource const&);
    
        public:
            T const get() const { return m_value; }
            operator T const() const { return get(); }
    
            void swap(unmanaged_resource& other) {
                using std::swap;
                swap(this->m_value, other.m_value);
                swap(this->m_deleter, other.m_deleter);
            }
    };
    
    // Example deleters:
    
    template <typename T>
    struct ptr_deleter : std::unary_function<T*, void> {
        void operator ()(T* ptr) const { delete ptr; }
    };
    
    template <typename T>
    struct array_deleter : std::unary_function<T*, void> {
        void operator ()(T* ptr) const { delete [] ptr; }
    };
    
    template <typename T>
    struct auto_ptr {
        typedef unmanaged_resource<T*, ptr_deleter<T> > type;
    };
    
    template <typename T>
    struct array_ptr {
        typedef unmanaged_resource<T*, array_deleter<T> > type;
    };
    
    template <typename T, typename F>
    inline void swap(
        unmanaged_resource<T, F>& a, unmanaged_resource<T, F>& b
    ) {
        a.swap(b);
    }
    
    int main() {
        typedef array_ptr<int>::type api_t;
        api_t ai(new int[10]);
        api_t bi(new int[5]);
    
        for (int i = 0; i < 10; ++i)
            ai[i] = i;
    
        using std::swap;
        swap(ai, bi);
    
        for (int i = 0; i < 10; ++i)
            std::cout << bi[i] << ' ';
        std::cout << std::endl;
    }
    

    (Mir ist bewusst, dass das ein sub-optimales Klassendesign ist und dass es in TR1 bereits etwas besseres gibt. Egal.)

    Nun, dieser Code funktioniert nicht, und zwar, weil der g++ in Zeile 71 immer versucht, 'std::swap' aufzurufen. Laut Meyers sollte er das doch eigentlich *nicht* tun und stattdessen über argument-dependent lookup darauf kommen, dass das 'swap' im globalen Namensbereich aufzurufen ist. Wenn ich die 'using'-Zeile weglasse oder den Namensbereich beim Aufruf von 'swap' explizit qualifiziere, kompiliert das Programm. Aber das will ich ja genau *nicht*.

    Wo ist der Fehler? Hat GCC 4.0.1 eventuell einen Fehler hier?



  • mit VS 2003 kompilierts mit warning

    scheiß spam filter
    ➡
    Sie müssen ein Thema für Ihre Antwort angeben.



  • warning C4675: 'void swap<T*,array_deleter<T>>(unmanaged_resource<T *,F>



  • &,unmanaged_resource<T *,F> &)': aufgelöste Überladung wurde mit argumentbezogener Suche gefunden
    with
    [
    T=int,
    F=array_deleter<int>
    ]



  • hmmm... schrieb:

    &,unmanaged_resource<T *,F> &)': aufgelöste Überladung wurde mit argumentbezogener Suche gefunden
    with
    [
    T=int,
    F=array_deleter<int>
    ]

    Hm, diese Warnung ist auf jeden Fall falsch, denn es gibt keine Überladung, in der 'T*' als partielle Spezialisierung vorkommt, und der Typenparameter 'T' müsste dementsprechend den Typ 'int*', nicht 'int' besitzen. Was ist da los? Wie kommt der auf diese Überladung?



  • Übrigens, mit VC++ 2005 kompiliert das ganze ohne Warnung.



  • So ganz versteh ich's nicht; du willst doch deine eigene swap-Methode statt der von std benutzen, richtig? Warum dann das using? Bzw wieso willst du das (ohne using) wiederum nicht?



  • Ich habe leider nicht die 3. Edition von "Effective C++" (nur die 2. und dort ist die swap-Implementation noch nicht beschrieben), aber ich habe aus einigen Beispielen im Internet gefolgert, daß du die swap-Spezialisierung innerhalb des "namespaces std" unterbringen solltest:

    namespace std // <--- hier rein
    {
    
    template <typename T, typename F>
    inline void swap(
        unmanaged_resource<T, F>& a, unmanaged_resource<T, F>& b)
    {
        a.swap(b);
    }
    
    } // namespace std
    

    So macht Scott Meyers es ja mit der swap-Funktion für die Widget-Klasse (wenn die Beispiele im Internet stimmen -)



  • Badestrand schrieb:

    So ganz versteh ich's nicht; du willst doch deine eigene swap-Methode statt der von std benutzen, richtig? Warum dann das using? Bzw wieso willst du das (ohne using) wiederum nicht?

    Lass mich raten: Du hast den Meyer nicht gelesen?

    Generell ist es so, dass 'std::swap' zum Tauschen zweier Instanzen ausreicht, da es templatisiert ist. In gewissen Fällen ist es aber nötig oder ratsam, die swap-Semantik auszutauschen, wie in meinem Fall. Konvention ist dann, dass man 'swap' als Member implementiert und 'std::swap' spezialisiert:

    struct foo {
        void swap(foo& other) { /* … */ }
    };
    
    namespace std {
        template <>
        void swap<foo>(swap& a, swap& b) {
            a.swap(b);
        }
    }
    

    Soweit so gut. Nur klappt dies nicht, wenn die Klasse 'foo' Templateparameter hat, denn dann müsste man 'std::swap' partiell spezialisieren, was in C++ nicht geht. Die Lösung ist, 'std::swap' nicht zu spezialisieren sondern zu überladen.

    Hier gibt es aber eine Einschränkung: Der Namensbereich 'std' darf *nicht* erweitert werden, das verbietet der Standard. Das Hinzufügen einer Spezialisierung wie oben ist erlaubt, das Hinzufügen einer Überladung hingegen nicht. Daher wird diese Methode nicht in 'std' untergebracht sondern in einem eigenen Namensbereich (oder halt im globalen):

    template <T>
    void swap(foo<T>& a, foo<T>& b) {
        a.swap(b);
    }
    

    Schön. Nur leider funktioniert es jetzt nicht mehr, wenn man folgendes schreibt:

    foo<int> a, b;
    std::swap(a, b);
    

    – logisch: 'std::swap' wollen wir ja auch gar nicht aufrufen, sondern '::swap'. Wenn 'foo' allerdings aus einer Bibliothek kommt, dann wissen wir mitunter nicht, wo der Autor der Bibliothek 'swap' für 'foo' implementiert hat. Um das Aussuchen dem Compiler zu überlassen und somit alle Möglichkeiten zuzulassen, binden wir bei der Benutzung zwar 'std:swap' (über 'using') ein, rufen 'swap' aber ohne explizite Qualifikation des Namensbereichs auf. Jetzt ist der Compiler in der Lage, sich die korrekte Version herauszusuchen (oder: zumindest sollte er das sein. Der GCC scheint es nicht zu tun).



  • Th schrieb:

    ich habe aus einigen Beispielen im Internet gefolgert, daß du die swap-Spezialisierung innerhalb des "namespaces std" unterbringen solltest:

    Siehe meine Antwort auf Badestrand. Solcher Code ist nur erlaubt, wenn die Klasse nicht templatisiert ist (beachte auch, dass Dein Code *keine* Spezialisierung sondern eine Überladung darstellt).


  • Mod

    Konrad Rudolph schrieb:

    Nun, dieser Code funktioniert nicht, und zwar, weil der g++ in Zeile 71 immer versucht, 'std::swap' aufzurufen. Laut Meyers sollte er das doch eigentlich *nicht* tun und stattdessen über argument-dependent lookup darauf kommen, dass das 'swap' im globalen Namensbereich aufzurufen ist.

    richtig.

    Wo ist der Fehler? Hat GCC 4.0.1 eventuell einen Fehler hier?

    Gut möglich, gcc 4.0 hat einige schwerwiegende Bugs - gibt es irgendeinen Grund, nicht auf 4.2.2 zu wechseln?

    Versuchsweise könntest du swap als normale friend-Funktion statt als Template definieren, möglicherweise tritt der Bug dann nicht auf.



  • Ich habe jetzt mal mit 3 verschiedenen IDEs (bzw. Compilern) folgenden Code ausprobiert (der deinem in etwa entspricht):

    #include <algorithm>
    
    namespace N
    {
    
    template<typename T>
    struct X
    {
    	void swap(X& x) { using std::swap; swap(t, x.t); };
    
    	T t;
    };
    
    /* // optional
    }
    
    namespace std
    {
    */
    
    template<typename T>
    void swap(N::X<T> &x, N::X<T> &y)
    {
    	x.swap(y);
    }
    
    }
    
    int main(int argc, char* argv[])
    {
    	using std::swap;
    
    	N::X<int> x, y;
    	swap(x, y);
    
    	return 0;
    }
    

    Der BCB5 compiliert, ruft aber die falsche Version der swap-Funktion auf (std::swap).

    Der TurboExplorer und der VS2005 compilieren und rufen einwandfrei die richtige Version auf. (sowohl im namespace N als auch in std).

    Versuche mal für den gcc deine Klasse in einen eigenen Namespace zu stecken (anstatt in den globalen, evtl. reicht ja auch ein anonymer Namesbereich).

    Oder aber du probierst mal eine neuere gcc-Version (4.1 oder 4.2)?



  • camper schrieb:

    Gut möglich, gcc 4.0 hat einige schwerwiegende Bugs - gibt es irgendeinen Grund, nicht auf 4.2.2 zu wechseln?

    Muss ich wohl mal. Das Kompilieren schreckt mich immer ab. Darwin Ports legt bei Source-Installationen immer mein gesamtes System lahm.



  • So, mit dem GCC 4.2.2 funktioniert es einwandfrei.

    Und eine Warnung an alle hier: Versucht nie, *nie*, den GCC mit der Option "-v" zu kompilieren!


Anmelden zum Antworten