简体   繁体   English

两步破坏对象的原因是什么?

[英]What is the reasoning behind two-step object destruction?

In the games development world I often see classes using separate initialize() and uninitialize() or shutdown() methods. 在游戏开发界,我经常看到使用单独的initialize()uninitialize()shutdown()方法的类。 This includes not only multiple tutorials, but also long-established and large real-world projects, like some modern game engines. 这不仅包括多个教程,还包括历史悠久的大型现实项目,例如一些现代游戏引擎。 I've recently seen a class in Cry Engine 3 which not only uses a shutdown() method, but goes as far as calling this.~Foo() from it, which based on everything I know about C++, can't really be considered a good design. 我最近在Cry Engine 3中看到了一个类,该类不仅使用shutdown()方法,而且甚至可以调用this.~Foo()基于我对C ++的了解而不能真正做到。被认为是一个好的设计。

While I can see some of the benefits that come from two-step initialization, and there are many discussions about it, I can't understand the reasoning behind the two-step destruction. 虽然我可以看到两步初始化带来的一些好处,并且有很多讨论,但是我无法理解两步破坏背后的原因。 Why not use the default facilities provided by C++ language in form of the destructor, but have a separate shutdown() method and the destructor left empty? 为什么不以析构函数的形式使用C ++语言提供的默认功能,而是使用单独的shutdown()方法并将析构函数留空? Why not go even further and, using modern C++, put all the resources held by an object into smart pointers so we don't have to worry about releasing them manually. 为什么不走得更远,并使用现代C ++将对象持有的所有资源放入智能指针中,这样我们就不必担心手动释放它们。

Is two-step destruction some outdated design based on principles that no longer apply or are there some valid reasons to use it over standard ways of controlling objects' lifetime? 两步销毁是基于不再适用的原理的一些过时的设计,还是出于某种合理的理由在控制对象生命周期的标准方法上使用它?

If you don't want to read, the gist is that you need exceptions to return errors from ctors and exceptions are bad. 如果您不想阅读,要点是, 您需要异常才能从ctor返回错误,并且异常很糟糕。

As Trevor and others have hinted at, there are a number of reasons for this practice. 正如Trevor和其他人所暗示的,这种做法有很多原因。 You've brought up a specific example here though, so let's address that. 不过,您在这里提出了一个具体的示例 ,因此让我们解决这个问题。

The tutorial deals with a class GraphicsClass (the name sure doesn't inspire confidence) which contains these definitions: 本教程处理了一个GraphicsClass类(名称肯定不会激发人们的信心),其中包含以下定义:

class GraphicsClass
{
public:
    GraphicsClass();
    ~GraphicsClass();
    bool Initialize(int, int, HWND);
    void Shutdown();
};

So it has ctor, dtor, and Initialize/Shutdown . 因此它具有ctor,dtor和Initialize/Shutdown Why not condense the latter into the former? 为什么不将后者浓缩为前者呢? The implementation gives a few clues: 该实现提供了一些线索:

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;
}

Ok sure, checking to see if new D3DClass fails is pointless (it only happens if we run out of memory and we've overridden new to not throw bad_alloc )*. 好的,确定new D3DClass失败没有意义的(仅在内存不足并且 我们重写new而不抛出bad_alloc情况下才会发生)*。 Checking to see D3DClass::Initialize() fails may not be though. 可能无法检查D3DClass::Initialize()失败。 As its signature hints at, it's trying to initialise some resources related to graphics hardware, which can sometimes fail in normal circumstances - maybe the resolution requested is too high, or the resource is in use. 正如其签名所暗示的那样,它正在尝试初始化一些与图形硬件相关的资源,这些资源在正常情况下有时可能会失败-可能是请求的分辨率太高或资源正在使用中。 We'd want to handle that gracefully, and we can't return errors in the ctor, we can only throw exceptions. 我们想要优雅地处理它,并且我们不能在ctor中返回错误,我们只能抛出异常。

Which of course raises the question: why don't we throw exceptions? 当然哪个提出了问题:我们为什么不抛出异常? C++ exceptions are very slow . C ++异常非常慢 So slow that opinions of it are very strong , especially in game development . 太慢了,对此的看法非常强烈特别是在游戏开发中 Plus you can't throw in the dtor , so have fun trying to say, put network resource termination there. 另外, 您不能丢掉dtor ,所以尝试说些有趣的话,将网络资源终止放在那儿。 Most, if not all, C++ games have been made with exceptions turned off. 大多数(如果不是全部)C ++游戏都是在异常关闭的情况下制作的。

That's the main reason anyway; 无论如何,这是主要原因。 I can't discount other, sometimes sillier, reasons though, such as having a C legacy (where there are no ctors/dtors), or an architecture that has pairs of modules A and B hold references to each other. 但是,我不能忽略其他(有时更愚蠢)的原因,例如拥有C遗留物(没有ctor / dtor)或具有一对模块A和B相互保持引用的体系结构。 Of course remember games development's #1 priority is to ship games, not create perfectly robust and maintainable architectures , so you sometimes see silly practices like this. 当然,请记住,游戏开发的第一要务是发布游戏,而不是创建完全健壮和可维护的架构 ,因此有时您会看到类似的愚蠢做法。

I hear that the C++ committee is deeply aware of the problems that exceptions have, but iirc the latest is that it's been put in the "too hard" bucket, so you'll see more of this in games for many years to come. 我听说C ++委员会深知异常会带来的问题,但是iirc的最新情况是它被放在了“太难了”的桶中,因此您将在未来很多年的游戏中看到更多此类异常。

*- Aha! *-啊哈! So checking to see if new D3DClass wasn't pointless, as we've probably disabled exceptions so this is the only way to check for failed memory alloc, among other things. 因此,检查new D3DClass 是否不是毫无意义的,因为我们可能已禁用了异常,因此这是检查失败的内存分配的唯一方法。

If your object has been allocated using placement new (ie: instantiated into a block of memory which wasn't allocated as a separate block by the system), then you need to call your object's destructor explicitly, as using the delete operator either explicitly or implicitly via a smart pointer would fail rather messily, as the system tried to deallocate that application-specified portion of the memory block. 如果您的对象是使用new放置方式分配的(即:实例化到系统未分配为单独块的内存块中),则需要显式调用对象的析构函数,如显式地使用delete运算符或当系统试图重新分配该应用程序指定的内存块部分时,通过智能指针隐式地失败将相当混乱。

You haven't given us anywhere near enough information to speculate about why the particular class you mention might be calling its destructor explicitly, it's not unreasonable to guess that this might be the reason, and the 'Shutdown()' call is just there to provide an interface around an explicit call to its destructor. 您没有给我们足够多的信息来推测为什么您提到的特定类可能会显式调用其析构函数,因此猜测这可能就是原因,因此不无道理,并且'Shutdown()'调用就在那里在对其析构函数的显式调用周围提供一个接口。 (so that end-users of the engine don't get it into their heads to try to call 'delete' on the object. Presumably they've made the destructor private as well, to further enforce their intended destruction API.) (这样,引擎的最终用户就不会想办法在对象上调用“删除”。大概他们也将析构函数设为私有的,以进一步执行其预期的销毁API。)

I would use this practice (with DTORs) for several reasons giving me more control and flexibility: 由于多种原因,我会使用这种做法(与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)
  }

It would have been better if I had a separate function that I can call when I want to do so.. (though I am not sure these are relevant to the specific game company that produced the code you referenced in your question. Having a saperate function to actually handle freeing resources gives you more flexibility and control over how and when those resources are deallocated. 如果我有一个单独的函数可以在需要时调用,那就更好了。(尽管我不确定这些函数是否与特定的游戏公司相关,这些公司生产了您在问题中引用的代码。)实际处理释放资源的功能可为您提供更大的灵活性,并控制如何以及何时释放这些资源。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM