The easiest way to make sure a local object is destructed before the end of its scope is to insert a pair of braces (that is, a new scope) so that the object will be destructed at the right time. For example, suppose the (desirable) side effect of destructing a local File object is to close the File. Now suppose a local File object needs to be closed before the end of the scope (i.e., the }) of its function. In this case simply add an extra pair of braces to scope the lifetime of the object: void sample() { // ... { <-- 1 File x; // ... <-- 2 // ... } <-- 3 // ... <-- 4 }
In cases where this extra pair of braces cannot be added, add an extra member function that causes the destructor's desirable side effects to occur. This member function should mark the object so that the destructor, which will inevitably be called at the close of the object's scope, will be able to tell if the side effects have already happened. For example, a close() member function could be added to the File class, and that member function could be called where the file should be closed: void sample() { // ... File x; // ... <-- 1 x.close(); <-- 2 // ... <-- 3 }
The close() member function could mark the object so the destructor knows not to reclose the file, such as setting the underlying file handle to some invalid value such as -1. To avoid duplication of code in the destructor and the close() member function, the destructor could simply call the close() member function, and the close() member function could check to see if the file handle is in the "already closed" state. #include <iostream> #include <stdexcept> using namespace std; int openFile(const char* name) throw() { // Normally this code would actually open the named file. // For this example, we pretend everything is handle 42. int handle = 42; cout << "opening " << name << " as #" << handle << '\n'; return handle; } void closeFile(int handle) throw() { // Normally this code would actually close the file cout << "closing file #" << handle << '\n'; } class File { public: File(const char* name) throw(); ~File() throw(); void close() throw(); protected: enum { closed_ = -1 }; <-- 1 int handle_; private: // These are never defined; copy semantics are ill defined File(const File&); File& operator= (const File&); }; inline void File::close() { if (handle_ != closed_) closeFile(handle_); handle_ = closed_; } inline File::File(const char* name) throw() : handle_( openFile(name) ) { } inline File::~File() throw() { close(); } void userCode(bool throwIt) throw(runtime_error) { File x("sample.txt"); cout << "after open, before throw-or-close\n"; if (throwIt) throw runtime_error("note that the file still gets closed!"); x.close(); cout << "after close\n"; } int main() { cout << "====== without throwing an exception ======\n"; userCode(false); cout << "====== with throwing an exception =========\n"; try { userCode(true); } catch (exception& e) { cout << "exception caught; " << e.what() << '\n'; } }
The output of this program follows. ====== without throwing an exception ====== opening sample.txt as #42 after open, before throw-or-close closing file #42 after close ====== with throwing an exception ========= opening sample.txt as #42 after open, before throw-or-close closing file #42 exception caught; note that the file still gets closed! It is important to note that closeFile() is called exactly once per open file, whether or not an exception is thrown or the x.close() instruction is reached. Even if x.close() were explicitly called twice, the underlying file would only be closed once. |