ScopeGuard: kopie vermeiden



  • Ich sehe zwar kein Problem, aber wenn du vom CopyCtor wegkommen willst, dann erstelle eine private Klasse im ScopeGuard die nur makeGuard instanziieren darf und verwende diese als initialisierung eines ScopeGuard objektes. Dann kannst du den CopyCtor private machen.

    alternativ geht auch ein movable ansatz ala Mojo...



  • mojo ist ein bisschen overkill für mein problem. deshalb verwende ich die variante mit der klasse, die scopeguard initialisiert. allerdings stellt sich da jetzt ein kleines hindernis:

    namespace detail
    {
    	template<class T>
    	struct foo
    	{
    		foo(T) {}
    	};
    } // detail
    
    class base {};
    
    template <class T>
    class bar : public base
    {
    	typedef detail::foo<T> foo;
    
    public:
    	bar(foo) {}
    
    private:
    	bar(const bar&);
    	bar& operator= (const bar&);
    };
    
    template<class T>
    detail::foo<T> fun(T t)
    {
    	return detail::foo<T>(t);
    }
    
    int main()
    {
    	const base& b = fun(2);
    }
    

    mein compiler findet den konstruktor von bar nicht (g++ 4.1.2). wie kann ich das beheben?

    copy.cpp:36: error: invalid initialization of reference of type 'const base&' from expression of type 'detail::foo<int>'



  • Also so spontan würde ich da mal sagen, dass das ist, weil es private ist...



  • drakon schrieb:

    Also so spontan würde ich da mal sagen, dass das ist, weil es private ist...

    das typedef ist private, aber da es eben nur ein alias ist, spielt das keine rolle.

    ich habe den code inzwischen vereinfacht (keine templates). der compiler erkennt immer noch nicht, dass es eine umwandlung von foo nach const base& gibt, allerdings habe ich keine ahnung warum. bar steht doch im selben namespace und der compiler sollte den konstruktor finden und als mögliche konvertierung erkennen.

    namespace detail
    {
    	struct foo
    	{
    		foo(int) {}
    
    	};
    } // detail
    
    class base {};
    
    class bar : public base
    {
    public:
    	bar(detail::foo) {}
    
    private:
    	bar(const bar&);
    	bar& operator= (const bar&);
    };
    
    detail::foo fun(int t)
    {
    	return detail::foo(t);
    }
    
    int main()
    {
    	const base& b = fun(2);
    }
    


  • es gibt auch keine umwandlung von detail::foo nach base.

    das ginge nur, wenn du fun ein bar zurückgeben lassen würdest.



  • ausgeloggt schrieb:

    es gibt auch keine umwandlung von detail::foo nach base.

    doch, gibt es (existiert):

    detail::foo -> bar -> const base&

    als test kann man folgendes in die main-funktion schreiben:

    const bar& a = fun(2);
    	const base& b = a;
    

    oder meinst du, es gibt keine umwandlung (findet keine statt), weil der compiler nicht nach einem entsprechenden konstruktor suchen muss?



  • es gibt keine Umwandlung von foo nach base. Es gibt nur eine Umwandlung von foo nach bar nach base.

    Was willst du eigentlich machen? Du kannst anhand der Daten die dir makeGuard() liefert das notwendige Objekt erstellen. Du musst das Objekt nicht sofort erstellen...

    oder: makeGuard kann zB das ScopeGuard objekt erstellen und du verwendest im Client Code nur einen ScopeGuard holder - wrappst also den echten ScopeGuard nur.



  • wenn ich nun aber nur ressourcen freigeben will (so wie mit boost::scoped_ptr z.b.) brauche ich den dismiss-mechanismus gar nicht.

    Wenn du nur Resourcen freigeben willst, dann bastel dir eigene RAII Klassen für diese Resourcen.

    Die "ScopeGuard" Lösung ist zwar nicht SEHR aufwändig, aber sicher aufwändiger als die Verwendung einer fertigen, spezialisierten RAII Klasse. Ohne diese RAII Klassen tippst du die ScopeGuard Lösung immer wieder und wieder und wieder, und überlegst dir immer wieder und wieder ob man da nicht was besser machen könnte.



  • hustbaer schrieb:

    Die "ScopeGuard" Lösung ist zwar nicht SEHR aufwändig, aber sicher aufwändiger als die Verwendung einer fertigen, spezialisierten RAII Klasse. Ohne diese RAII Klassen tippst du die ScopeGuard Lösung immer wieder und wieder und wieder, und überlegst dir immer wieder und wieder ob man da nicht was besser machen könnte.

    Aeh... nicht wirklich.

    Du tippst:

    FILE* f=fopen();
    ON_BLOCK_EXIT(fclose, f);
    

    Das ist oft weitaus besser als die komplette api zu wrappen. Denn man braucht ScopeGuard genau dann wenn du es mit einer C API zu tun hast. Und die komplette API in schoenes C++ zu wrappen ist nicht trivial und kostet sehr viel zeit.



  • Äh, doch wirklich.
    Ich weiss schon was du meinst.
    Ich hab selbst öfters solche oder ähnliche Dinge gebraucht. Ich schlage auch nicht vor die komplette API zu wrappen, sondern einfach nur RAII Klassen zu basteln:

    // Version 1 (flexibel, schnell zu schreiben, einfach)
    class FileHandle : private boost::noncopyable
    {
    public:
        FileHandle() : m_file(0) {}
        explicit FileHandle(FILE* file) : m_file(file) {}
    
        ~FileHandle()
        {
            if (m_file)
                fclose(m_file);
        }
    
        FILE* m_file; // ja, public!
    };
    
    // Version 2 ("schöner")
    class FileHandle : private boost::noncopyable
    {
    public:
        FileHandle() : m_file(0) {}
        explicit FileHandle(FILE* file) : m_file(file) {}
    
        ~FileHandle()
        {
            if (IsValid())
                fclose(m_file);
        }
    
        FILE* Get() const;
        bool IsValid() const;
    
        void Reset(FILE* file = 0);
        FILE* Release();
    
    private:
        FILE* m_file;
    };
    

    Dadurch reduziert sich das ganze in der Anwendung auf:

    FileHandle f(fopen(...));
    

    Was einfacher ist.
    Sobald man ein FILE* o.ä. als Member in einer Klasse braucht wird der Unterschied noch deutlicher:

    // mit ScopeGuard
    Foo::Foo(...)
    {
        m_file = fopen(...);
        ScopeGuard fileGuard = makeGuard(&fclose, m_file);
        // ... ein haufen anderes Zeug welches Exceptions werfen oder sonstwie schief gehen könnte ...
        fileGuard.Dismiss();
    }
    
    Foo::~Foo()
    {
        fclose(m_file);
    }
    
    // mit spezialisierter RAII Klasse:
    Bar::Bar(...)
    {
        m_file.Reset(fopen(...));
        // ... ein haufen anderes Zeug welches Exceptions werfen oder sonstwie schief gehen könnte ...
    }
    
    Bar::~Bar()
    {
    }
    


  • Ich persoenlich mag keine halben Loesungen.

    Es ist zeitweise durchaus praktisch solche minimalen wrapper zu schreiben - aber oft reicht ein scopeguard locker aus. ein scopeguard kann naemlich auch member sein - das bitte nicht vergessen 😉

    manchmal finde ich es einfach besser keine klasse nur wegen RAII zu verwenden: beispielsweise wenn ich mir speicher ueber eine spezielle alloc Funktion hole, zB GlobalAlloc. Ich mag den rohen Speicher nicht wirklich kapseln und mir den Aufwand einer neuen Klasse antun. Genau fuer solche Sachen ist ScopeGuard einfach ideal.

    fuer FILE* wuerde ich wohl eher einen vollwertigen wrapper basteln - aber das lustige an ScopeGuard ist ja, ich bekomme exakt deine Wrapper Klasse automatisch:

    ScopeGuard m = makeGuard(fclose, fopen("foo","w"));
    if(m.get()!=NULL) {
    }
    

    klappt problemlos.



  • Also was eine "halbe lösung" ist ist ja wohl sehr subjektiv 🙂
    Ich halte hier eher den ScopeGuard für die "halbe lösung".

    Und das "m.get()" kann IMO nicht funktionieren da ScopeGuard nix davon weiss dass ein FILE* drinsteckt. Wenn dann müsste man also schon das entsprechende Template direkt verwenden anstatt es in einem ScopeGuard zu verstecken...



  • hustbaer schrieb:

    Also was eine "halbe lösung" ist ist ja wohl sehr subjektiv 🙂
    Ich halte hier eher den ScopeGuard für die "halbe lösung".

    Und das "m.get()" kann IMO nicht funktionieren da ScopeGuard nix davon weiss dass ein FILE* drinsteckt. Wenn dann müsste man also schon das entsprechende Template direkt verwenden anstatt es in einem ScopeGuard zu verstecken...

    stimmt natuerlich - dazu muss man etwas rumbasteln. ScopeGuard<FILE*> guard = makeGuard(fclose, f); oder man verwendet generell ein Makro zur Definition. Das ganze ist aber nur dann relevant wenn man es wirklich als Member einer klasse haben will - in den meisten Faellen reicht ein ON_BLOCK_EXIT() locker.

    die ganze idee hinter scopeguards ist es ja den unnoetigen aufwand fuer jede resource eigene wrapper zu schreiben zu eliminieren. man spart sich aufwand - denn jeder programmierer sollte 2mal denken wenn er im prinzip den selben code X mal schreiben muss.

    scopeguard bietet die selbe funktionalitaet wie deine minimalen wrapper klassen - kostet mich aber nur minimalen aufwand.



  • Der Grund ScopeGuard zu entwickeln war übrigens auch, daß viele Entwickler jegliche Vorsicht unter Zeitdruck fallen lassen. Da ist es für diejenigen einfacher schnell OB_BLOCK_EXIT reinzuhacken als ne RAII-Klasse zu schreiben.



  • Also ich habe ScopeGuard immer so verstanden dass man es hernimmt um Cleanup durchzuführen welcher in der Form nur selten gebraucht wird.
    Wenn ich eine potentielle "Cleanup-Klasse" bloss 1 oder 2x brauche ziehe ich auch eine Lösung ala ScopeGuard vor.
    Wenn ich das zig- oder hundertfach brauche mach ich mir lieber eine eigene Klasse.


Anmelden zum Antworten