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

C++ RAII to manage change and reversion of object state

本文关键字:恢复 状态 管理 对象 C++RAII      更新时间:2023-10-16

我有一个类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:。我不能使用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((之前出现write(((。

EDIT正如Nawaz在下面指出的,一个真正的类还需要一个复制构造函数和赋值运算符。

为了确保调用close((,这似乎是一个很大的样板。出现两个问题:

  1. 这比强迫类的用户使用try/catch还简单吗?

  2. 有没有一种更简单的方法可以实现我想要的:提供基本的异常保证,并确保close((始终跟在open((后面?

嵌套类opener应该实现复制语义,因为如果我正确理解您的意图,编译器生成的默认代码会产生不希望的结果。

所以请实现复制构造函数和复制赋值。

或者,您可能希望通过使它们的声明1private完全禁用复制语义,这与所有标准流类的实现非常相似。我更喜欢这种方法。

1.请注意,您不需要定义它们。仅在private部分中声明它们就足够了

关闭是否会失败?如果可以的话,那么无论采取什么方法,你都需要格外小心。不过,我认为RAII方法比强制用户进行异常处理/关闭更简单。

foo的创建和销毁真的如此复杂吗(或者它是全局的吗?(以至于你不能只让它的析构函数调用close而不是使用opener来进行匹配的打开/关闭?

或者,如果这是在实现某种事务语义,我看不到比opener类更简单的方法(但正如其他答案中所指出的,您可能希望禁用opener类的复制和赋值(。

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

  • 一个类,用于存储贯穿始终的状态
  • 一个类,用于处理打开/关闭中的瞬态,它还负责所有"瞬态"操作,如写入

"transient"类将"data"类作为参数(通过引用(,并将在各种方法调用期间更新它。

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

这是使用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");
}

您可以向类添加一个标志,如果调用open((,则该标志设置为true,如果调用close((,该标志将设置为false。如果调用open((,则可以检查标志是true还是false,如果需要,则在继续之前关闭。