C ++ / CLI析构函数调用乱序 [英] C++/CLI destructor called out of order

查看:69
本文介绍了C ++ / CLI析构函数调用乱序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我们有这个代码:



Say we have this code:

Thing^ MyManagedObject = gcnew Thing();
MyManagedObject->DoStuff();
MyManagedObject = gcnew Thing();
//MyManagedObject is now random memory





在第二个gcnew上它创建新的Thing并将引用放在 MyManagedObject ,它继续为Thing调用析构函数。唯一的问题是,它没有将析构函数称为已超出范围的Thing,而是我的新Thing,我正在引用。



我虽然问题仅限于托管对象,但即使在发布版本中运行一些非托管对象也会让事情变得混乱,举个例子:





On the second gcnew it creates the new Thing and puts the reference in MyManagedObject, it the goes on to call the destructor for Thing. The only problem is that it doesn''t call the destructor for the Thing that has gone out of scope, but my new Thing that I''m holding the reference to.

I though the problem was limited to managed objects but even with some unmanaged objects when running in release it messes things up, take this example:

Matrix Transform = OtherMatrix; //OtherMatrix successfully copied to Transform
//Do Stuff
Transform = OtherMatrix * SomethingElse;





乘法矩阵创建了一个新的矩阵,然后在被复制到 Transform 之前被破坏。



进行一些调试复制ctor /赋值运算符中的输出(两者都发生相同的情况)和destrutor中你在输出中得到的结果



The multiplication of the matrices created a new matrix, which is then destructed before being copied into Transform.

Putting some debug output in the copy ctor / assignment operator (same thing happens with both) and in the destrutor you get this in the output

Destroying matrix: 0x0028f140
Copying matrix: 0x0028f140 to 0x0028f080



而不是相反。



有没有人遇到过这个?我正在使用VS2012这个,但也许我会在2010年打开它,看看它是否设法搞砸了。



我不能成为唯一一个有这个问题的人,也许我做了一些愚蠢的事,也许别人做了些蠢事但是最好还是修理一下。



< ------------------------------------>

编辑:案例1是由我自己的愚蠢造成的(长话短说,需要自定义复制等)



案例2似乎是一些优化的问题。我还没弄清楚如何解决这个问题,或者为什么现在决定开始给我提问。我的矩阵类是模板化的,浮点数有一个特殊化,它使用sse指令来处理很多函数。乘法运算符如下所示:




Rather than the other way around.

Has anybody come across this before? I''m using VS2012 for this one, but maybe I''ll open it up in 2010 and see if it manages to screw things up just as good.

I can''t be the only one with this issue, maybe I did something stupid, maybe somebody else did something stupid but there''d better be a fix.

<------------------------------------>
Case 1 was caused by my own stupidity (long story short, custom copy ctor etc. required)

Case 2 seems to be an issue with some optimisation. I''ve not figured out how to get around it or why it''s decided to start giving me issues now. My matrix class is templated, there is a specialisation for floats that uses sse instructions for a lot of the functions. The multiplication operator looks like this:

(Matrix& A, Matrix& B)
{
 Matrix<float> Result;
 //Load addresses of A, B and Result to registers
 //Do multiplication using sse 
 //store final matrix in Result
 return Result;
}





结果最终得到正确的值,但是乘法的调用者永远不会看到它。 Matrix Result = MatA * MatB; //结果以所有0'的结尾



如果我修改结果的值,它似乎是返回值优化asm,我看到了变化,然而在asm中完成的任何事情都没有被看到。奇怪的是,编译器过去一直很聪明(或者没有试图过于聪明)以避免这个问题。



Result ends up with the correct values, but the caller of the multiplication never sees it. Matrix Result = MatA * MatB; //Result ends up with all 0''s

It seems to be return value optimisation, if I modify values for the result outside of the asm, I see the changes, anything done in the asm however is not seen. Strange that the compiler has been clever enough (or not tried to be too clever) in the past to avoid this issue.

推荐答案

你永远不应该依赖在.NET中调用析构函数的顺序或时间。在托管系统中使用析构函数的想法和实践与使用C ++和类似语言的传统对象生命周期控制系统完全不同。因此,与C ++相比,C ++ / CLI中的构造函数编写得非常非常少。 在大多数情况下,它们只是不需要,因为大多数析构函数是由垃圾收集器(GC)完成的,并且......完全相同的原因:你不能依赖析构函数的时间或顺序电话。



GC操作基于对象的可达性的概念。在应用程序中无法预测对象析构函数的实际调用和回收内存的行为(顺便说一句,这就是为什么析构函数很少在典型的.NET应用程序中编写的原因,通常这不是必需的,并且可能会产生一些问题种族条件类型),但当某个物体变得无法到达时会发生。



顺便说一句,这个并不像你想象的那么简单。例如,如果对象A引用对象B,对象B引用对象C和C引用A,则此循环依赖性不会阻止GC确定如果没有其他对象引用,则应对所有对象进行垃圾回收。通过简单的实验很容易检查。



您可以在这里找到可达性和垃圾收集的详细信息:http://en.wikipedia.org/wiki/Garbage_collection_%28computer_science%29 [ ^ ]。



请参阅我过去的回答: 当CLR执行自动内存管理和GC? [ ^ ]。



祝你好运,

-SA
You should never rely on the order or time of calling destructors in .NET. The idea and practice of using destructors in a managed system is radically different from "traditional" object lifetime control systems used with C++ and similar languages. By this reason, constructors in C++/CLI are written very, very rarely, compared to C++. In most cases, they are just not needed, because most of the destructor chores are done by the Garbage Collector (GC), and… by exact same reason: you cannot rely on the time or order of destructor calls.

GC operation is based on the notion of reachability of an object. The actual call of the object destructors and the act of reclaiming memory cannot be predicted bu the application (by the way, this is the reason why destructores are rarely written in the typical .NET applications, usually this is not needed and can created some problems of race conditions type), but it happens when some object becomes unreachable.

By the way, this is not so trivial criterion as you might think. For example, if object A references object B, object B references object C, and C reference A, this cyclic dependency does not prevent GC from figuring out that all objects should be garbage-collected if there are no other referenced to them. This is easy to check up by a simple experiment.

You can find the detail of reachability and garbage collection here: http://en.wikipedia.org/wiki/Garbage_collection_%28computer_science%29[^].

Please see also my past answer: When CLR Do Automatic Memory Management and GC?[^].

Good luck,
—SA


让我们这样说:如果你声称真的是真的,那么没有C ++ / CLI可以可能成功运行。这就是你如此惊讶的原因。通常情况下,解决方案可能会出现在您用来解释结果的方式中测量此行为的代码中。



所以我的建议是:向我们展示析构函数和复制构造函数代码,用于打印您从中获得此解释的输出。



此外,您展示的两个案例非常不同关于内存分配方案。第一个示例是关于引用类,第二个示例是关于值类。那么在你的第一个例子中理论上应该发生什么:

Let''s put it this way: If what you claim would really be true, no C++/CLI could possibly run successfully. That''s the reason you are so surprised. And how so often, the solution might be found in the code with which you are measuring this behaviour o rin the way you are interpreting the result.

So my recommendation is: Show us the destructor and copy-constructor code that prints those outputs from which you are deriving this interpretation.

In addition, the two cases you are showing are very different regarding the memory allocation scheme. The first example is about a reference class, the second about a value class. So what should happen in theory in your first example:
Thing^ MyManagedObject = gcnew Thing();
MyManagedObject->DoStuff();
MyManagedObject = gcnew Thing();



第二个gcnew调用为<分配新的引用code> MyManagedObject 。因此,丢失了对第一个Thing对象的引用。而已。 GC会在一段时间后清理它;可能不是马上,但后来有数百或数千个gcnew电话。因此,如果第一个 Thing 对象的析构函数没有被调用很长时间,甚至可能是程序结束时,我也不会感到惊讶。



现在,考虑到这个新的期望,你检查你的Thing类的代码,看它是否允许这种解释。



你的第二个例子是一个完全不同的案例:


The second gcnew call assigns a new reference to MyManagedObject. Thereby, the reference to the first Thing object is lost. Nothing more. The GC will clean it up some time later; probably not right away but many hundred or thousand gcnew calls later. So I wouldn''t be surprised if the destructor for the first Thing object wasn''t called for a long time, perhaps even as late as the end of your program.

Now, perhaps with this new expectation in mind, you check your code of the Thing class and see if it would allow that interpretation.

Your second example is a totally different case:

Matrix Transform = OtherMatrix; //OtherMatrix successfully copied to Transform
Transform = OtherMatrix * SomethingElse;





Matrix显然是一个值类,它的内存分配在堆栈上,而不是堆。实际上不应该在这里调用 Transform 对象的析构函数,而是希望执行赋值运算符。编译器生成用于将OtherMatrix与SomethingElse相乘的代码,结果是在堆栈上分配的Matrix类型的临时对象。将此临时对象分配给Transform后,必须将其销毁。这可能是你正在观察的析构函数。所以我期望的顺序是:



Matrix is obviously a value class and its memory allocated on the stack, not the heap. The destructor for the Transform object should actually not be called here, instead I would expect the assignment operator to be executed. The compiler generates code for multiplying OtherMatrix with SomethingElse and the result is a temporary object of type Matrix that is allocated on the stack. This temporary object must be destroyed after assigning it to Transform. That is perhaps the destructor call you are observing. So the sequence I would expect is:

constructor call for creating the temporary object
operator*
assignment operator to transfer the result to Transform
destructor call to destroy the temporary object



请查看析构函数中的打印代码并复制构造函数,看看是否以正确的方式解释结果。请随意使用您的打印和测试代码修改您的问题,这些代码会引导您进行此假设,我们将对其进行审核。我非常肯定的是,在尘埃落定之后,一切都将再次落实到位。


Please review the printing code in the destructor and copy constructors and see if you are interpreting the results in the right way. Feel free to amend your question with your printing and test code that led you to this assumption and we will take a look at it. I am quite positive that after the dust settles everything will fall into place again.


>>矩阵的乘法创建了一个新矩阵,

>>然后在被复制到Transform之前被破坏。



我最好的猜测是你在任务操作员中遇到了一些麻烦。



我只能假设你有这样的东西:

>> The multiplication of the matrices created a new matrix,
>> which is then destructed before being copied into Transform.

My best guess is that you''re having some trouble inside the assignment operator.

I can only assume that you have something like this:
class Matrix
{
  struct MatrixData
  {
    long referenceCount;
    int rows, int cols;
    double[0] data;
  };
  Matrix();
  Matrix(int rows, int cols, double* data);
  Matrix(const Matrix& otherMatrix);
  
  // I guess something strange is going on inside 
  // the assignment operator, perhaps a delete this??
  Matrix& operator = (const Matrix& otherMatrix);  

};





祝你好运

Espen Harlinn



Best regards
Espen Harlinn


这篇关于C ++ / CLI析构函数调用乱序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆