c++design-patternsscopeguard

Dynamically created scope guards


I've read the article about scope guards (Generic: Change the Way You Write Exception-Safe Code — Forever) in DDJ and I understand their common use.

However, the common use is to instantiate a particular stack guard on the stack for a particular operation, e.g.:

{
    FILE* topSecret = fopen("cia.txt");
    ON_BLOCK_EXIT(std::fclose, topSecret);
    ... use topSecret ...
} // topSecret automagically closed

but what if I want to schedule cleanup operations in runtime, e.g. when I have a loop:

{
   vector<FILE*> topSecretFiles;
   for (int i=0; i<numberOfFiles; ++i)
   {
      char filename[256];
      sprintf(filename, "cia%d.txt", i);
      FILE* topSecret = fopen(filename);
      topSecretFiles.push_back(topSecret);
      ON_BLOCK_EXIT(std::fclose, topSecret); // no good
   }
}

Obviously, the above example wouldn't work, since topSecret would be closed along with the for scope. I'd like a scope guard pattern where I can just as easily queue up cleanup operations which I determine to be needed at runtime. Is there something like this available?

I can't push scope guard objects into a standard queue, cause the original object (the one I'm pushing) would be dismissed in the process. How about pushing heap-allocated stack guards and using a queue which deletes its members on dtor? Does anyone have a more clever approach?


Solution

  • It seems you don't appreciate RAII for what it is. These scope guards are nice on occasion for local ("scope") things but you should try to avoid them in favour of what RAII is really supposed to do: encapsulating a resource in an object. The type FILE* is really just not good at that.

    Here's an alternative:

    void foo() {
        typedef std::tr1::shared_ptr<FILE> file_sptr;
        vector<file_sptr> bar;
        for (...) {
            file_sptr fsp ( std::fopen(...), std::fclose );
            bar.push_back(fsp);
        }
    }
    

    Or:

    void foo() {
        typedef std::tr1::shared_ptr<std::fstream> stream_sptr;
        vector<stream_sptr> bar;
        for (...) {
            file_sptr fsp ( new std::fstream(...) );
            bar.push_back(fsp);
        }
    }
    

    Or in "C++0x" (upcoming C++ standard):

    void foo() {
        vector<std::fstream> bar;
        for (...) {
            // streams will become "movable"
            bar.push_back( std::fstream(...) );
        }
    }
    

    Edit: Since I like movable types in C++0x so much and you showed interest in it: Here's how you could use unique_ptr in combination with FILE* without any ref-counting overhead:

    struct file_closer {
        void operator()(FILE* f) const { if (f) std::fclose(f); }
    };
    
    typedef std::unique_ptr<FILE,file_closer> file_handle;
    
    file_handle source() {
        file_handle fh ( std::fopen(...) );
        return fh;
    }
    
    int sink(file_handle fh) {
        return std::fgetc( fh.get() );
    }
    
    int main() {
        return sink( source() );
    }
    

    (untested)

    Be sure to check out Dave's blog on efficient movable value types