У меня есть класс foo. Операции над foo требуют вызова foo::open(), ряда foo::write() и должны заканчиваться вызовом foo::close():
#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);
}
Как и ожидалось, это выводит следующее:
foo::foo()
foo::open()
foo::write(string1)
foo::write(string2)
foo::close()
foo::open()
foo::write(string1)
foo::write(string2)
foo::close()
foo::~foo()
Я хочу дать пользователям моего класса foo способ гарантировать, что они не забудут вызвать foo::close(), и гарантировать, что foo::close() будет вызываться в случае возникновения исключения. Я не могу использовать деструктор foo, так как foo должен продолжать существовать после foo::close(), готовый к следующему foo::open().
Я придумал эту реализацию RAII:
#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);
}
Для простоты я не включил очевидное улучшение, заключающееся в том, что класс foo::opener предоставляет метод foo::write(), хотя в реальном объекте я бы сделал это, чтобы предотвратить возможность write() до open( ).
EDIT Как указывает Наваз ниже, реальному классу также потребуется конструктор копирования и оператор присваивания.
Это кажется довольно шаблонным только для того, чтобы гарантировать, что вызов close() будет вызван. Возникают два вопроса:
Это все же проще, чем заставлять пользователей моего класса использовать try/catch?
Есть ли более простой способ добиться того, что я хочу: предоставить базовую гарантию исключения и убедиться, что close() всегда следует за open()?