gpt4 book ai didi

c++ - C++/CLI 中的重复析构函数调用和跟踪句柄

转载 作者:IT老高 更新时间:2023-10-28 22:37:52 24 4
gpt4 key购买 nike

我正在使用 C++/CLI,使用 MSDN 文档和 ECMA standard和 Visual C++ Express 2010。让我印象深刻的是以下与 C++ 的背离:

For ref classes, both the finalizer and destructor must be written so they can be executed multiple times and on objects that have not been fully constructed.



我编造了一个小例子:
#include <iostream>

ref struct Foo
{
Foo() { std::wcout << L"Foo()\n"; }
~Foo() { std::wcout << L"~Foo()\n"; this->!Foo(); }
!Foo() { std::wcout << L"!Foo()\n"; }
};

int main()
{
Foo ^ r;

{
Foo x;
r = %x;
} // #1

delete r; // #2
}

在块的末尾 #1 ,自动变量 x死亡,并调用析构函数(它依次显式调用终结器,就像通常的习惯用法一样)。这一切都很好。但是后来我通过引用再次删除了对象 r !输出是这样的:

Foo()
~Foo()
!Foo()
~Foo()
!Foo()

问题:
  • 调用 delete r 是未定义的行为,还是完全可以接受?在线#2 ?
  • 如果我们删除行 #2 ,这有关系吗r仍然是(在 C++ 意义上)不再存在的对象的跟踪句柄?是“悬空 Handlebars ”吗?它的引用计数是否意味着会尝试双重删除?

    我知道没有实际的双重删除,因为输出变成了这样:
    Foo()
    ~Foo()
    !Foo()

    但是,我不确定这是否是一次愉快的事故,还是保证是明确定义的行为。
  • 在哪些其他情况下可以多次调用托管对象的析构函数?
  • 插入x.~Foo();可以吗紧接在之前或之后 r = %x; ?

  • 换句话说,托管对象是否“永远存在”并且可以一遍又一遍地调用它们的析构函数和终结器?

    针对@Hans 对非平凡类的需求,您也可以考虑这个版本(使用析构函数和终结器来满足多次调用的要求):
    ref struct Foo
    {
    Foo()
    : p(new int[10])
    , a(gcnew cli::array<int>(10))
    {
    std::wcout << L"Foo()\n";
    }

    ~Foo()
    {
    delete a;
    a = nullptr;

    std::wcout << L"~Foo()\n";
    this->!Foo();
    }

    !Foo()
    {
    delete [] p;
    p = nullptr;

    std::wcout << L"!Foo()\n";
    }

    private:
    int * p;
    cli::array<int> ^ a;
    };

    最佳答案

    我将尝试按顺序解决您提出的问题:

    For ref classes, both the finalizer and destructor must be written so they can be executed multiple times and on objects that have not been fully constructed.



    析构函数 ~Foo()简单地自动生成两个方法,一个 IDisposable::Dispose() 方法的实现以及一个实现一次性模式的 protected Foo::Dispose(bool) 方法。这些是普通方法,因此可以多次调用。 C++/CLI 允许直接调用终结器, this->!Foo()并且通常完成,就像您一样。垃圾收集器只调用一次终结器,它会在内部跟踪是否已完成。鉴于允许直接调用终结器并且允许多次调用 Dispose(),因此可以多次运行终结器代码。这是特定于 C++/CLI 的,其他托管语言不允许。您可以轻松阻止它,nullptr 检查通常可以完成工作。

    Is it undefined behavior, or is it entirely acceptable, to call delete r on line #2?



    它不是UB,完全可以接受。 delete运算符只需调用 IDisposable::Dispose() 方法,从而运行您的析构函数。您在其中所做的事情,通常是调用非托管类的析构函数,很可能会调用 UB。

    If we remove line #2, does it matter that r is still a tracking handle



    不。调用析构函数是完全可选的,没有一个很好的方法来强制执行它。没有任何问题,终结器最终将始终运行。在给定的示例中,当 CLR 在关闭之前最后一次运行终结器线程时会发生这种情况。唯一的副作用是程序运行“繁重”,占用资源的时间超过了必要的时间。

    Under which other circumstances can the destructor of a managed object be called more than once?



    这是很常见的,一个过度热心的 C# 程序员可能会多次调用您的 Dispose() 方法。提供 Close 和 Dispose 方法的类在框架中很常见。有些模式几乎是不可避免的,例如另一个类承担对象的所有权。标准示例是这段 C# 代码:
    using (var fs = new FileStream(...))
    using (var sw = new StreamWriter(fs)) {
    // Write file...
    }

    StreamWriter 对象将取得其基本流的所有权,并在最后一个花括号处调用其 Dispose() 方法。 FileStream 对象上的 using 语句第二次调用 Dispose()。编写此代码以防止这种情况发生并仍然提供异常保证太困难了。指定可以多次调用 Dispose() 可以解决问题。

    Would it be OK to insert x.~Foo(); immediately before or after r = %x;?



    没关系。结果不太可能令人愉快,NullReferenceException 将是最有可能的结果。这是您应该测试的内容,引发 ObjectDisposedException 以便为程序员提供更好的诊断。所有标准的 .NET 框架类都这样做。

    In other words, do managed objects "live forever"



    不,垃圾收集器会声明该对象已死亡,并在它无法再找到对该对象的任何引用时收集它。这是内存管理的故障安全方式,没有办法意外引用已删除的对象。因为这样做需要一个引用,一个 GC 将始终看到的引用。循环引用等常见的内存管理问题也不是问题。

    Code snippet



    删除 a object 是不必要的,没有效果。您只能删除实现 IDisposable 的对象,数组不会这样做。通用规则是 .NET 类仅在管理内存以外的资源时才实现 IDisposable。或者,如果它有一个本身实现 IDisposable 的类类型的字段。

    此外,在这种情况下是否应该实现析构函数也是值得怀疑的。您的示例类持有一个相当适度的非托管资源。通过实现析构函数,您将负担强加给客户端代码以使用它。这在很大程度上取决于客户程序员这样做的难易程度,这绝对不是如果对象预计会存活很长时间,超出方法的主体,因此 using 语句不可用.您可以让垃圾收集器知道它无法跟踪的内存消耗,调用 GC::AddMemoryPressure()。这也解决了客户端程序员根本不使用 Dispose() 的情况,因为它太难了。

    关于c++ - C++/CLI 中的重复析构函数调用和跟踪句柄,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12240297/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com