使用C ++与Java和C#配置模式

时间:2015-08-01 18:46:00

标签: c++ resources raii

我有一些Java背景(最近在C#中),并希望更好地了解C ++。我想我已经意识到这些语言之间内存(和其他资源)管理差异的一些基础知识。这可能是一个小问题,涉及使用dispose pattern以及这些语言中提供的不同功能来协助它。我喜欢我收集的RAII and SBRM原则,并且我试图进一步理解它们。

假设我在Java中有以下类和方法

class Resource implements Closeable {
    public void close() throws IOException {
        //deal with any unmanaged resources
    }
}
...
void useSomeResources() {
    try(Resource resource = new Resource()) {
        //use the resource
    }
    //do other things. Resource should have been cleaned up.
}

或相当接近的C#模拟

class Resource : IDisposable
{
    public void Dispose()
    {
        //deal with any unmanaged resources
    }
}
...
void UseSomeResources()
{
    using(var resource = new Resource())
    {
        //use the resource
    }
    //do other things. Resource should have been cleaned up.
}

我是否认为在C ++中最能代表同样行为的成语如下?

class Resource {
    ~Resource() {
        cleanup();
    }
    public:
    void cleanup() {
        //deal with any non-memory resources
    }
};
...
void useSomeResources()
{
    {
        Resource resource;
        //use the resource
    }
    //do other things. Stack allocated resource
    //should have been cleaned up by stack unwinding
    //on leaving the inner scope.
}

我特别想要引出关于谁的语言更好以及类似的东西的争论,但我想知道这些实现可以在多大程度上进行比较,以及它们对案例的强大程度使用资源的块遇到异常情况。我可能完全错过了关于某些事情的观点,而且我从未完全确定处理的最佳实践 - 为了争论,也许值得假设这里的所有处置/破坏功能都是幂等的 - 并且真的这些问题的好建议也可能与这个问题有关。

感谢您的任何指示。

5 个答案:

答案 0 :(得分:3)

这几乎就是模式。事实上,您不需要添加cleanup()函数:析构函数可以进行清理。

顺便说一句,暴露公共cleanup()允许意外调用cleanup(),使资源处于不受欢迎的状态。

class Resource {
    ~Resource() {
        //deal with any non-memory resources
    }
};   // allways ; at the end of a class ;-)

答案 1 :(得分:2)

(1)提议的课程,

class Resource {
    ~Resource() {
        cleanup();
    }
    public:
    void cleanup() {
        //deal with any non-memory resources
    }
};

是非惯用且危险的,因为(1)它公开cleanup操作,(2)它阻止从中派生类,并阻止此类的自动变量。

任何代码都可以随时调用公开的cleanup,清理后您将拥有不可用的僵尸对象。并且您不知道何时或是否发生这种情况,因此实现代码必须检查每个状态。很不好意思。它与init函数相同,它们扮演构造函数的角色,只有一个虚拟构造函数。

实际上不能派生类,因为在对象被销毁的派生类中,会生成对此类的析构函数的调用,并且析构函数不可访问 - 因此代码将无法编译。

正确的模式如下所示:

class Resource
{
public:
    // Whatever, then:

    ~Resource()
    {
        // Clean up.
    }
};

仍然可以明确地调用析构函数,但是有强烈的动机不这样做。

请注意,使用类派生和多态使用时,最好使用析构函数virtual。但在其他情况下,这将不必要地使类多态,因此具有大小成本。所以这是一个工程决策。

(1)我添加了一个缺失的分号。发布真实代码是个好主意,即使对于一般的小例子也是如此。 功能

答案 2 :(得分:0)

您已经提到了答案,它是RAII,就像您的链接一样。

C ++中的典型类将有一个(虚拟的!你忘记了)析构函数:

  class C { 
    virtual ~C { /*cleanup*/ }
  };

您可以使用常规块规则控制其生命周期:

  void f() {
    C c;

    // stuff

    // before this exits, c will be destructed
  }

事实上,像C#和Java这样的语言尝试使用它们的配置模式进行模拟。由于它们没有确定性终结器,因此您必须手动释放非托管资源(分别使用usingtry)。但是,C ++完全是确定性的,所以这样做要简单得多。

答案 3 :(得分:0)

感谢您的任何指示。哈!

您指的是Java try with resources方法,这是实际调用resource.close()的快捷方式。另一种选择是调用resource.Dispose()

要记住的重要一点是,您在Java和C#中使用的对象使用这些接口关闭事物需要对象和成员字段关闭。打开后必须关闭文件。没有办法解决它,并试图躲开你的方式,将让你高度干燥的记忆,并将使其他应用程序面临失败的风险,因为没有访问你声称但从未有过的文件关闭。提供代码以使文件关闭非常重要。

但是当物体留下记忆时,还有其他一些东西需要摆脱。当这些物体离开范围时,就会发生这些事情。这就是C ++中的析构函数,你在上面引用的内容被调用。

Closeable和IDisposable属于我所谓的“负责任的”类。当它从范围中移除对象并释放可用指针的顶级内存时,它们超出了类的任何正常“析构函数”。他们还会处理您可能没有想到的事情的严峻考验,或者可能会使系统面临风险。这就像是一个父亲而不是一个好父亲。父亲必须给他的孩子提供庇护,但是一个好父亲知道什么对孩子最好,即使孩子或其他看护人不知道什么对他们最好。

请注意,当您想要使用Java的“try with Resources”替代方案时,引用AutoCloseable接口不一定是Closeable接口很重要。

答案:IDisposableCloseable界面,甚至AutoCloseable界面都支持删除托管资源。 C ++中的“析构函数”也是如此,它是这种删除过程的简写的祖父。问题是您仍然必须确保正确处理正在遭受破坏的类的成员。我认为你有正确的功能来调用C ++来做你想做的事情。

参考文献: http://www.completecsharptutorial.com/basic/using.php http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html

答案 4 :(得分:0)

总结在这个问题的其他答案中提出的一些好点以及我读过的其他一些事情:

  • 主要问题的答案是肯定的,automatic local variable resource调用了析构函数,无论控件如何离开定义的块。在这方面,变量的内部范围和局部分配(通常意味着堆栈而不是堆,但依赖于编译器)(而不是使用new)的行为非常类似于Java中的try-with-resources块,或者在C#中使用块。
  • 与Java和C#相比,在C ++中纯粹本地(通常意味着:堆栈)分配对象的能力意味着,对于处理需要安全处理的资源的对象,额外的接口实现和有些过度曝光的公共处理方法不需要(通常不可取)。
    • 使用private析构函数~Resource(),可以消除意外状态中意外出现对象的一些危险(例如没有文件句柄的文件编写器),但是“非托管资源”仍然总是安全地处理掉删除对象(如果它是问题示例中的自动局部变量,则超出范围。)
    • 如果需要,使用public清理功能成员仍然是绝对可能的,但这通常是不必要的危险。如果清理成员必须公开,最好是破坏者本身,因为这是一个明显的“自我记录”指示,任何用户只能在非常罕见的情况下调用它:更好地使用{{1或者让本地分配的对象超出范围,让编译器完成调用析构函数的工作。它还消除了非析构函数公共方法可能导致的任何混淆('我应该在delete此对象之前调用cleanup()吗?')。
    • 如果要继承资源对象,重要的是要确保其析构函数同时为delete(可覆盖)和(至少与virtual一样可见),以确保子类可以是妥善处理。
  • 此外,直接在析构函数中实现清理,并在离开自动变量的范围后立即执行无垃圾收集器的破坏语义(以及dynamically-allocated variablesprotected),它变为类型本身的财产和责任必须妥善处置,而不是只能安全处置。

更惯用的C ++用法示例:

delete

当按照问题中的建议使用时,这种方法应该确保资源得到安全处理而不会发生泄漏。