C ++ RAII用于管理对象状态的更改和还原

时间:2011-10-05 14:20:14

标签: 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()方法的明显改进,尽管在实际对象中我这样做是为了防止write()在此之前成为可能一个开放的()。

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

这似乎是为了确保调用close()而花费大量的样板。出现了两个问题:

  1. 这是否比强迫我的班级用户使用try / catch更简单?

  2. 有没有更简单的方法来实现我想要的:提供基本的异常保证并确保close()始终跟在open()之后?

5 个答案:

答案 0 :(得分:2)

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

所以请实现copy-constructor和copy-assignment。

或者,您可能希望通过使其声明 1 private完全禁用复制语义,就像implementation of all standard stream classes一样。我更喜欢这种方法。

1。请注意,您无需定义它们。只需在private部分声明它们即可。

答案 1 :(得分:1)

关闭会失败吗?如果可以,那么无论采取何种方法,您都需要格外小心。我认为RAI​​I方式比强迫用户处理/关闭异常更简单。

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

或者,如果这是实现某种事务语义,我看不到比opener类更简单的方法(但在其他答案中你可能想要禁用复制和分配opener类)。

答案 2 :(得分:1)

我认为你应该分开你的担忧:

  • 一个用于存储整个
  • 的状态的类
  • 一个类来处理打开/关闭中的瞬态状态,它还处理所有“瞬态”操作,如write

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

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

答案 3 :(得分:0)

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

答案 4 :(得分:0)

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