C++ RAII 管理对象状态的更改和恢复
我有一堂课 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() 方法的明显改进,尽管在真实对象中我会这样做以防止写入() 在 open() 之前是可能的。
编辑 正如 Nawaz 在下面指出的那样,真正的类还需要复制构造函数和赋值运算符。
这似乎有很多样板只是为了确保调用 close() 。出现两个问题:
这仍然比强制我的类的用户使用 try/catch 更简单吗?
有没有更简单的方法来实现我想要的:提供基本的异常保证并确保 close() 始终遵循 open()?
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:
Is this still simpler than forcing the users of my class to use a try/catch?
Is there a simpler way to achieve what I want: provide the basic exception guarantee and ensure that close() always follows open()?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
嵌套类 opener 应该实现复制语义,因为如果我正确理解您的意图,编译器生成的默认代码将产生不良结果。
所以请实现复制构造函数和复制赋值。
或者,您可能希望通过将其声明1
private
来完全禁用复制语义,就像所有标准流类的实现。我更喜欢这种方法。1.请注意,您不需要定义它们。只需在
private
部分声明它们就足够了。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.close 会失败吗?如果可以,那么无论采用哪种方法,您都需要格外小心。我认为 RAII 方法比强制用户进行异常处理/关闭更简单。
foo
的创建和销毁真的如此复杂(还是全局的?)以至于您不能只让其析构函数调用 close 而不是使用 opener 来执行匹配的打开/关闭?或者,如果这是实现某种事务语义,我看不到比 opener 类更简单的方法(但正如其他答案中所述,您可能希望禁用 opener 类的复制和分配)。
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).
我认为你应该分开你的关注点:
“瞬态”类需要“数据”类作为参数(通过引用)并将在各种方法调用期间更新它。
然后,您可以在瞬态类上使用典型的 RAII,并且仍然可以在整个类中传播状态。
I think you should separate your concerns:
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.
这是使用 RAII 的经典案例。请使用构造函数和析构函数:如果您想再次
open()
,请实例化一个新的foo
。 RAII 的思想是实例化代表单一资源使用。这就是您获得资源清理保证的方式。This is a classic case for using RAII. Do use the constructor and destructor: if you want to
open()
again, instantiate a newfoo
. The idea of RAII is that an instantiation represents a single resource use. That's how you get the guarantee for resource cleanup.您可以向类添加一个标志,如果调用 open(),则设置为 true;如果调用 close(),则设置为 false。如果调用 open(),您可以检查该标志是 true 还是 false,并在继续之前关闭(如果需要)。
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.