C++ RAII 管理对象状态的更改和恢复

发布于 2024-12-08 07:55:45 字数 2888 浏览 0 评论 0原文

我有一堂课 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() 。出现两个问题:

  1. 这仍然比强制我的类的用户使用 try/catch 更简单吗?

  2. 有没有更简单的方法来实现我想要的:提供基本的异常保证并确保 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:

  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()?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

云之铃。 2024-12-15 07:55:45

嵌套类 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.

奈何桥上唱咆哮 2024-12-15 07:55:45

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).

空心空情空意 2024-12-15 07:55:45

我认为你应该分开你的关注点:

  • 一个类来存储整个类中携带的状态,
  • 以处理打开/关闭中的瞬态状态,它还负责所有“瞬态”操作,例如写入

“瞬态”类需要“数据”类作为参数(通过引用)并将在各种方法调用期间更新它。

然后,您可以在瞬态类上使用典型的 RAII,并且仍然可以在整个类中传播状态。

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.

乄_柒ぐ汐 2024-12-15 07:55:45

这是使用 RAII 的经典案例。请使用构造函数和析构函数:如果您想再次open(),请实例化一个新的foo。 RAII 的思想是实例化代表单一资源使用。这就是您获得资源清理保证的方式。

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");
}

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");
}
伊面 2024-12-15 07:55:45

您可以向类添加一个标志,如果调用 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文