[英]What is the reasoning behind two-step object destruction?
在游戏开发界,我经常看到使用单独的initialize()
和uninitialize()
或shutdown()
方法的类。 这不仅包括多个教程,还包括历史悠久的大型现实项目,例如一些现代游戏引擎。 我最近在Cry Engine 3中看到了一个类,该类不仅使用shutdown()
方法,而且甚至可以调用this.~Foo()
基于我对C ++的了解而不能真正做到。被认为是一个好的设计。
虽然我可以看到两步初始化带来的一些好处,并且有很多讨论,但是我无法理解两步破坏背后的原因。 为什么不以析构函数的形式使用C ++语言提供的默认功能,而是使用单独的shutdown()
方法并将析构函数留空? 为什么不走得更远,并使用现代C ++将对象持有的所有资源放入智能指针中,这样我们就不必担心手动释放它们。
两步销毁是基于不再适用的原理的一些过时的设计,还是出于某种合理的理由在控制对象生命周期的标准方法上使用它?
如果您不想阅读,要点是, 您需要异常才能从ctor返回错误,并且异常很糟糕。
正如Trevor和其他人所暗示的,这种做法有很多原因。 不过,您在这里提出了一个具体的示例 ,因此让我们解决这个问题。
本教程处理了一个GraphicsClass
类(名称肯定不会激发人们的信心),其中包含以下定义:
class GraphicsClass
{
public:
GraphicsClass();
~GraphicsClass();
bool Initialize(int, int, HWND);
void Shutdown();
};
因此它具有ctor,dtor和Initialize/Shutdown
。 为什么不将后者浓缩为前者呢? 该实现提供了一些线索:
bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
bool result;
// Create the Direct3D object.
m_D3D = new D3DClass;
if(!m_D3D)
{
return false;
}
// Initialize the Direct3D object.
result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
if(!result)
{
MessageBox(hwnd, L"Could not initialize Direct3D", L"Error", MB_OK);
return false;
}
return true;
}
好的,确定new D3DClass
失败是没有意义的(仅在内存不足并且 我们重写new
而不抛出bad_alloc
情况下才会发生)*。 可能无法检查D3DClass::Initialize()
失败。 正如其签名所暗示的那样,它正在尝试初始化一些与图形硬件相关的资源,这些资源在正常情况下有时可能会失败-可能是请求的分辨率太高或资源正在使用中。 我们想要优雅地处理它,并且我们不能在ctor中返回错误,我们只能抛出异常。
当然哪个提出了问题:我们为什么不抛出异常? C ++异常非常慢 。 太慢了,对此的看法非常强烈 , 特别是在游戏开发中 。 另外, 您不能丢掉dtor ,所以尝试说些有趣的话,将网络资源终止放在那儿。 大多数(如果不是全部)C ++游戏都是在异常关闭的情况下制作的。
无论如何,这是主要原因。 但是,我不能忽略其他(有时更愚蠢)的原因,例如拥有C遗留物(没有ctor / dtor)或具有一对模块A和B相互保持引用的体系结构。 当然,请记住,游戏开发的第一要务是发布游戏,而不是创建完全健壮和可维护的架构 ,因此有时您会看到类似的愚蠢做法。
我听说C ++委员会深知异常会带来的问题,但是iirc的最新情况是它被放在了“太难了”的桶中,因此您将在未来很多年的游戏中看到更多此类异常。
*-啊哈! 因此,检查new D3DClass
是否不是毫无意义的,因为我们可能已禁用了异常,因此这是检查失败的内存分配的唯一方法。
如果您的对象是使用new放置方式分配的(即:实例化到系统未分配为单独块的内存块中),则需要显式调用对象的析构函数,如显式地使用delete运算符或当系统试图重新分配该应用程序指定的内存块部分时,通过智能指针隐式地失败将相当混乱。
您没有给我们足够多的信息来推测为什么您提到的特定类可能会显式调用其析构函数,因此猜测这可能就是原因,因此不无道理,并且'Shutdown()'调用就在那里在对其析构函数的显式调用周围提供一个接口。 (这样,引擎的最终用户就不会想办法在对象上调用“删除”。大概他们也将析构函数设为私有的,以进一步执行其预期的销毁API。)
由于多种原因,我会使用这种做法(与DTOR一起使用),这给了我更多的控制和灵活性:
// Passing by value to modify some things without affecting the original instances
somefunc(Foo f, Bar b)
{
// Behind the scene (during construction), the original instances allocated
// and now share some resources with these copies
// Modifying and testing here
.
.
.
// Implicit call to DTOR in 9 .. 8 .. 7
// DTOR was called here implicitly before exiting the scope
// (I may not have actually wanted to free some shared resources)
}
如果我有一个单独的函数可以在需要时调用,那就更好了。(尽管我不确定这些函数是否与特定的游戏公司相关,这些公司生产了您在问题中引用的代码。)实际处理释放资源的功能可为您提供更大的灵活性,并控制如何以及何时释放这些资源。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.