【问题标题】:Difference between Release() and destructor?Release() 和析构函数的区别?
【发布时间】:2019-12-18 04:32:54
【问题描述】:

当我学习使用抽象接口将代码封装在动态库中时,似乎建议使用“Release()”函数来释放资源。现在我想知道为什么他们不只是使用析构函数来释放?使用析构函数释放资源是否有任何问题,或者它们只是意味着使用智能指针?

这是推荐的代码:

// Interface like that ..
struct IXyz
{
    virtual int Foo(int n) = 0;
    virtual void Release() = 0;
};

// Xyz class definition, derived from IXyz
// ...


// Using in client ..
IXyz* pXyz = GetXyz();  //Use Factory function to create an object

if(pXyz)
{
    pXyz->Foo(42);

    pXyz->Release();
    pXyz = nullptr;
}

现在我想编写以下代码:

// Interface
struct IXyz
{
    virtual int Foo(int n) = 0;
    virtual ~IXyz() {};    // I have moved Release()'s content into Xyz's destructor
};


// Client
IXyz* pXyz = GetXyz();  //Use Factory function to create an object

if(pXyz)
{
    pXyz->Foo(42);

    pXyz->~IXyz();
    pXyz = nullptr;
}

这些代码都可以正常工作。所以我想知道这两种释放资源的方式之间的区别。我可以同时使用它们吗?非常感谢!

【问题讨论】:

  • 我认为这个链接会对你有所帮助 (stackoverflow.com/questions/16908650/…)
  • 发布与删除不同。 release 是代理对象的工厂函数。 delete 将内存返回到内存池。
  • 您的第二个代码块泄漏了IXyz 对象的内存。您需要正确释放它。如何做到这一点取决于GetXyz 做了什么。如果你跳过Release,你也需要修改它。 GetXyz 在你的例子中做了什么?
  • 这就是麻烦的开始。它会泄漏内存吗?程序在使用删除时会爆炸吗?客户端代码无法知道工厂函数做了什么。返回指向全局变量的指针是完全有效的,new 运算符也是如此。当你使用 Release() 时没关系,实现总是知道如何正确地做到这一点。
  • @H3d9 正如上面的 cmets 中提到的,问题是您无法确定 如何 删除该对象,因为您不知道它是如何分配的.为避免此问题,GetXyz 应返回一个智能指针,如果您不想手动调用 Release,该指针知道如何为您处理释放。

标签: c++


【解决方案1】:

这只是 Windows 上的问题。当您通过 new 分配内存时,Win32 可以使用您的 DLL 或 EXE 本地的分配堆。这可能会导致您在发布构建 DLL 中分配但稍后在调试构建 EXE 中释放的问题。然后调试堆会抛出一个错误,说内存分配是未知的。

所以要解决这个问题,一般的方法是确保在 DLL 堆中为 DLL 对象分配内存,并在同一个堆中释放。

首先,让我们尝试您在上面描述的工厂方法。提供一个 DLL 导出的工厂方法来分配内存,例如

struct IXyz
{
  EXPORT static IXyz* create();
};

// in cpp file
EXPORT IXyz* IXyz::create()
{
  return new IXyz;
}

然而,这意味着您必须在同一个地方释放内存,您可以通过以下两种方式之一来完成。选项一,提供 DLL 导出的发布方法。

struct IXyz
{
  EXPORT static IXyz* create();
  EXPORT void release();
};

// in cpp file
EXPORT void IXyz::release()
{
  delete this;
}

另一种方法是使 dtor 虚拟化。它将确保在同一位置释放分配,但会通过 vtable 指针使您的结构大小膨胀。

如果您不介意这样做,另一种方法是从基本 DLL 导出一对自定义 malloc/free 函数(简单地调用全局 new/delete)。然后在从 DSO 导出的每个类中重载 new/delete。使用 new/delete 将不再给您带来问题。

除非您实际上大量使用 DLL,并且您需要混合调试/发布版本,否则以上所有内容都是毫无意义的。这些是有其位置的模式,但一般来说它们不是很好的模式。

【讨论】:

  • 谢谢,你是对的。据我所知,最好的方法是在析构函数中释放资源,并在释放函数中删除。这将自动调用析构函数并释放内存。
【解决方案2】:

首先,调用析构函数会破坏对象,但不会释放它的内存。实际上,很少需要显式调用析构函数,因为delete(对于动态分配的对象)或超出范围(对于分配在堆栈上/全局范围内的对象)已经调用了析构函数并处理了内存.

现在,Release() 实际上做了一些非常不同的事情:它(至少在我所知道的所有库中)是引用计数的一部分。这是一种控制对象何时应为delete-ed 的方法,具体取决于使用它的事物的数量。因此,当您调用析构函数(显式地或通过调用delete)肯定会破坏对象时,调用Release() 只是告诉库您不再使用该对象,并且库可以随时删除它感觉像 - 现在可能不是,因为库可能仍在内部使用该对象。

【讨论】:

  • 不过,最好使用类似shared_ptr 的指针包装器来处理。
猜你喜欢
  • 2010-12-24
  • 2020-10-18
  • 2023-03-10
  • 2011-01-16
  • 2012-12-08
  • 2013-07-26
  • 2021-06-01
相关资源
最近更新 更多