1

I have a class foo. Operations on foo require a call to foo::open(), a number of foo::write(), and must end with a foo::close() call:

#include <iostream>

class foo
{
public:
    foo()
    {
        std::cout << "foo::foo()" << std::endl;
    }
    ~foo()
    {
        std::cout << "foo::~foo()" << std::endl;
    }    
    void open()
    {
        std::cout << "foo::open()" << std::endl;
    }
    void close()
    {
        std::cout << "foo::close()" << std::endl;
    }
    void write(const std::string& s)
    {
        std::cout << "foo::write(" << s << ")" << std::endl;
    }    
private:    
    // state that must be retained for entire lifetime of object
};

static void useFoo(foo& my_foo)
{
    my_foo.open();
    my_foo.write("string1");
    my_foo.write("string2");
    my_foo.close();
}

int main(  int argc, char* argv[] )
{
    foo my_foo;
    useFoo(my_foo);
    useFoo(my_foo);
}

As expected, this outputs the following:

foo::foo()
foo::open()
foo::write(string1)
foo::write(string2)
foo::close()
foo::open()
foo::write(string1)
foo::write(string2)
foo::close()
foo::~foo()

I want to give users of my class foo a way of ensuring that they don't forget to call foo::close(), and to ensure that foo::close() gets called if an exception happens. I can't use foo's destructor as foo must continue to exist after a foo::close(), ready for the next foo::open().

I came up with this RAII implementation:

#include <iostream>

class foo
{
public:
    class opener
    {
    public:
        explicit opener(foo& my_foo):foo_(my_foo)
        {
            foo_.open();
        };
        ~opener()
        {
            foo_.close();
        };    
    private:
        foo& foo_;
    };    
    foo()
    {
        std::cout << "foo::foo()" << std::endl;
    }
    ~foo()
    {
        std::cout << "foo::~foo()" << std::endl;
    }    
    void open()
    {
        std::cout << "foo::open()" << std::endl;
    }
    void close()
    {
        std::cout << "foo::close()" << std::endl;
    }
    void write(const std::string& s)
    {
        std::cout << "foo::write(" << s << ")" << std::endl;
    } 
    opener get_opener()
    {
        return(opener(*this));
    }   
private:
    // state that must be retained for entire lifetime of object    
};


static void useFoo(foo& my_foo)
{
    foo::opener my_foo_opener = my_foo.get_opener();
    my_foo.write("string1");
    my_foo.write("string2");
}

int main(  int argc, char* argv[] )
{
    foo my_foo;
    useFoo(my_foo);
    useFoo(my_foo);
}

For simplicity I haven't included the obvious improvement of having the foo::opener class expose the foo::write() method, though in a real object I'd do this to prevent a write() being possible before an open().

EDIT As Nawaz points out below, a real class would also need a copy constructor and assignment operator.

This seems quite a lot of boilerplate just to ensure that a close() gets called. Two questions arise:

  1. Is this still simpler than forcing the users of my class to use a try/catch?

  2. Is there a simpler way to achieve what I want: provide the basic exception guarantee and ensure that close() always follows open()?

Simon Elliott
  • 2,087
  • 4
  • 30
  • 39

5 Answers5

2

The nested class opener should implement the copy-semantics, as the default code generated by the compiler would produce undesirable result, if I correctly understood your intention.

So please implement copy-constructor, and copy-assignment.

Or alternatively, you may want to disable copy-semantic altogether, by making their declarations1 private, much like implementation of all standard stream classes. I would prefer this approach.

1. Note that you don't need to define them. Just declaring them in the private section is enough.

Community
  • 1
  • 1
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 3
    Good point. Or make the copy constructor and assignment operator private, if there was no use case for copying. – Simon Elliott Oct 05 '11 at 14:29
  • 1
    By using `const foo::opener & my_foo_opener = my_foo.get_opener();` it doesn't create a copy – crazyjul Oct 05 '11 at 14:43
  • @crazyjul: Yes. And if he disables the copy-semantic, as I suggested, then he is left with that option, i.e to work with *reference* only. – Nawaz Oct 05 '11 at 14:45
  • @Nawaz Yes, working with a reference looks like the most useful option. The only remaining problem is to handle the case where foo is destroyed before foo::opener. I've seen people use smart pointers for this. – Simon Elliott Oct 05 '11 at 14:53
  • 2
    In fact, you should _not_ define the private copy mechanisms. Otherwise, if by mistake you do use them (in the class or its friends), you could only find out about it at runtime. That's a bug that might be hard to trace. If you don't define them and use them by mistake your linker would spot the error. – wilhelmtell Oct 06 '11 at 12:20
1

Can close ever fail? If it can, then you're going to need to take extra care regardless of approach. I think the RAII way is simpler than forcing exception handling/closing on your users though.

Is foo really so complex (or is it a global?) to create and destroy that you can't just have its destructor call close instead of using the opener to do the matching open/close?

Or if this is implementing some sort of transaction semantics I can't see a simpler way than the opener class (but as noted in other answers you probably want to disable copying and assignment of the opener class).

Mark B
  • 95,107
  • 10
  • 109
  • 188
  • Yes, I'll need to give some thought about how to handle failures, particularly in close(). foo retains some state so can't be destroyed and recreated. – Simon Elliott Oct 05 '11 at 14:34
1

I think you should separate your concerns:

  • one class to store the state that is carried throughout
  • one class to handle the transient state within a open/close, which also takes care of all the "transient" operations like write

The "transient" class takes the "data" class as parameter (by reference) and will update it during the various method calls.

Then you can use typical RAII on the transient class and still have state propagated throughout.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
0

This is a classic case for using RAII. Do use the constructor and destructor: if you want to open() again, instantiate a new foo. The idea of RAII is that an instantiation represents a single resource use. That's how you get the guarantee for resource cleanup.

struct foo {
    foo() { open(); }
    ~foo() { close(); }
    void write(const std::string& s);
private:  // or public
    foo(const foo&);
    foo& operator=(const foo&);
};

// ...
{ foo fooa;
    fooa.write("hello 0");
} { foo foob;
    foob.write("hello 1");
}
wilhelmtell
  • 57,473
  • 20
  • 96
  • 131
  • He wants to open it using the same object, again! – Nawaz Oct 05 '11 at 14:32
  • foo retains some state so can't be destroyed and recreated. This may not have been clear from my example. I wanted to keep the example as simple as possible, maybe it was too simple. I've added a comment to the private section of the class to indicate that the object must retain some state. – Simon Elliott Oct 05 '11 at 14:36
  • @wilhelmtell: The standard stream classes provide this feature. You can use the same stream object to open and close, then open another file. – Nawaz Oct 05 '11 at 14:37
0

You could add a flag to the class, that is set to true if you called open() and to false if you called close(). If open() is called you can check if the flag is true or false and and close if you need to do so before proceeding.

sta
  • 456
  • 1
  • 3
  • 10
  • That would work, and would also allow a check for write() without a preceding open(). However it wouldn't call close() if there was an uncaught exception or at the program exit, as it would only call close() if it was followed by an open(). I think the RAII solution is probably simpler and more deterministic. – Simon Elliott Oct 05 '11 at 14:46
  • Which ever way you prefer! You could however also check the flag within the destructor. Since when an exception occurs the destructor should be called, you could check the flag there and call close() before destroying the object. – sta Oct 05 '11 at 14:51